Using System.Timers in visual studio for simulation/animation

Hey guys,

Ive been messing around in visual studio trying to figure out how to do time-based simulation without using the timer component within grasshopper. I created a simple counter that should add one every second, but instead, it seems to spit out the result*result. (e.g. 0, 1, 2, 4, 8, 16 etc) until it eventually crashes. wondering if anyone knows what I’m doing wrong?

Below the relevant sections:

public class TimerTestComponent : GH_Component
{
  Timer timer = new Timer();
  double n = 0;
  bool reset, run;

  public TimerTestComponent()
    : base("TimerTest", "TimeT", "Used to test time-based components", "User", "Debug")
    {
      timer.Interval = 1000;
      timer.Start();
    }
    protected override void RegisterInputParams(GH_Component.GH_InputParamManager pManager)
    {
      pManager.AddBooleanParameter("Run", "RUN", ".", GH_ParamAccess.item);
      pManager.AddBooleanParameter("Reset", "RES", ".", GH_ParamAccess.item);
      pManager.AddNumberParameter("Number", "N", "number to be affected.", GH_ParamAccess.item);
    }

    protected override void RegisterOutputParams(GH_Component.GH_OutputParamManager pManager)
    {
      pManager.AddNumberParameter("Result", "R", "Result.", GH_ParamAccess.item);
    }

    protected override void SolveInstance(IGH_DataAccess DA)
    {
      if (!DA.GetData(0, ref run)) return;
      if (!DA.GetData(1, ref reset)) return;
      if (reset) if (!DA.GetData(2, ref n)) { return; }

      if (run)
      {
        Update();
        n++;
      }
      DA.SetData(0, n);
    }

    public void Update()
    {
      timer.Elapsed += new ElapsedEventHandler(UpdateSolution);
    }

    public void UpdateSolution(object source, ElapsedEventArgs e)
    {
      ExpireSolution(true);
    }
  }
}

This is the method I often use

using Grasshopper.Kernel;
using Grasshopper.Kernel.Data;
using Grasshopper.Kernel.Parameters;
using Grasshopper.Kernel.Types;
using Rhino.Geometry;
using System;
using System.Collections.Generic;
using System.Drawing;
using Grasshopper;

namespace Pneu_Solver
{
  public class TimerParticleAdditionComponent :  GH_Component
  {
    private bool on;
    private bool reset;
    private double c;
    private double cMod;
    private Curve curve;

    public override Guid ComponentGuid { get { return new Guid("{24bfd06e-d88b-451c-a0f3-5d29c577b275}"); }}
    protected override System.Drawing.Bitmap Icon  {  get  {  return Properties.Resources.TimerParticleAddition;  }}

    //Schedule a new solution after a specified time interval
    private void ScheduleCallBack(GH_Document doc) => this.ExpireSolution(false);

    protected override void AfterSolveInstance()
    {
      if (!this.on) return;
      GH_Document ghDocument = base.OnPingDocument();
      if (ghDocument == null) return;
      ghDocument.ScheduleSolution(1, new GH_Document.GH_ScheduleDelegate(this.ScheduleCallBack));
    }

    public TimerParticleAdditionComponent() 
      : base("Timer", "Timer", "Timer", "Kangaroo2", "Timers") {}

    protected override void RegisterInputParams(GH_InputParamManager pManager)
    {
      pManager.AddBooleanParameter("On", "On", "On", GH_ParamAccess.item, false);
      pManager[0].Optional = true;
      pManager.AddBooleanParameter("Reset", "Reset", "Reset", GH_ParamAccess.item, false);
      pManager[1].Optional = true;
      pManager.AddCurveParameter("Polyline", "Polyline", "Polyline", GH_ParamAccess.item);
    }

    protected override void RegisterOutputParams(GH_OutputParamManager pManager)
    {
      pManager.AddGenericParameter("Out", "Out", "Out", GH_ParamAccess.item);
      pManager.AddCurveParameter("Point", "Point", "Point", GH_ParamAccess.item );
    }

    protected override void SolveInstance(IGH_DataAccess DA)
    {
      DA.GetData(0, ref this.on);
      DA.GetData(1, ref this.reset);
      DA.GetData(2, ref this.curve);

      this.c += 0.1;
      if (this.reset) this.c = 0;

      cMod = c % 1.0;

      DA.SetData(0, this.curve.PointAt(cMod));
    }
  }
}

System.Timer does not execute on the same thread that created the timer. That is probably where the problem lies.

public TimerTestComponent() : base("TimerTest", "TimeT", "Used to test time-based components", "User", "Debug") {
      timer.Interval = 1000;
      timer.Start(); 
}

You can’t start the timer from here. You assume GH only calls the constructor when the component is added to the document, but I think it also calls it in another context. Not sure about this, but I’d bet a beer on it.

 public void Update()
{ 
    timer.Elapsed += new ElapsedEventHandler(UpdateSolution); 
}

This doesn’t do what you think it does. This line subscribes a method to be executed when the event is triggered. You’re subscribing to it every iteration. You only have to subscribe it once, a good place is in the same context where you specify the interval for example (in the constructor in this case). So:

 public TimerTestComponent() : base("TimerTest", "TimeT", "Used to test time-based components", "User", "Debug") {
          timer.Interval = 1000;
          timer.Elapsed += new ElapsedEventHandler(UpdateSolution);
    }

I have never used System.Timers.Timer, I always use System.Windows.Forms.Timer. I don’t think it matters, but what matters is stopping the timer at some point.

if(run) timer.Start();
else timer.Stop();

No tested:

public class TimerTestComponent : GH_Component
{

    Timer timer = new Timer();
    int counter;
    int maxCounter;
    bool reset, run;

    public TimerTestComponent()
      : base("TimerTest", "TimeT",
          "Used to test time-based components",
          "User", "Debug")
    {
        timer.Interval = 1000;
        timer.Elapsed += new ElapsedEventHandler(UpdateSolution);
    }
     
    protected override void SolveInstance(IGH_DataAccess DA)
    {
        //I would not leave the SolveInstance without making sure that the timer is stopped... but well
        if (!DA.GetData(0, ref run)) return;
        if (!DA.GetData(1, ref reset)) return;
        if (!DA.GetData(2, ref maxCounter)) return;

        if (reset) Reset();

        if (run && counter < maxCounter && !timer.Enabled)
        {
            Start();
        }
        else if(timer.Enabled){
            Stop();
        }

        DA.SetData(0, counter);

    }

    public void Start() { 
        counter = 0; 
        timer.Start(); 
    }
    public void Stop() {
        timer.Stop();
    }
    public void Reset()
    {
        counter = 0;
    }
    public void Update()
    {
        // DoSomethingEpic
        counter++;
    }

    public void UpdateSolution(object source, ElapsedEventArgs e)
    {
        Update();
        ExpireSolution(true);
    }
}
1 Like

You just won a beer. All components are constructed once when GH loads.

In general you don’t want to do any heavy lifting inside constructors if at all possible. The AddedToDocument and RemovedFromDocument and MovedBetweenDocuments methods provide better places for constructing and releasing expensive objects such as Timers.

1 Like

This worked great! thx!

good to know. Unfortunately, I was not able to get it to work. probably put something somewhere it doesnt belong. Gotta investigate when I have some time.