Dynamic timer Speed Update

Hello guys,

So I’m making this very simple code that makes a counter.
When I change the slider the counter doesn’t speed up or slow down unless I press reset it every time.

I want the counter speed to change when change value of the slider without having to press reset everytime.
Any help would be much appreciated.

here is the VS code:

using System;
using System.Collections.Generic;
using Grasshopper.Kernel;
using Rhino.Geometry;
 
namespace SurfaceTrails2.Mesh
{
  public class MeshIterativeMove : GH_Component
  {
   public System.Windows.Forms.Timer timer;
   public int counter;
  
   private bool reset, run;

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

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

   public MeshIterativeMove()
     : base("Mesh Iterative move Attractor", 
            "MeshIterativeMove",
            "Moves mesh's closest vertices to attractor point iteratively",
            "YFAtools", "Mesh") { }

   protected override void RegisterInputParams(GH_Component.GH_InputParamManager pManager)
   {
     pManager.AddBooleanParameter("Run", "Run", "run the Component", GH_ParamAccess.item, false);
     pManager.AddBooleanParameter("Reset", "Rst", "Reset timer to 0", GH_ParamAccess.item, false);
     pManager.AddIntegerParameter("TimeInterval", "Int", "The time interval between iteration in milliseconds",GH_ParamAccess.item, 500);
     pManager.AddIntegerParameter("MaximumIterations", "Max", "Maximum number of Iterations", GH_ParamAccess.item, 0);
   }
   protected override void RegisterOutputParams(GH_Component.GH_OutputParamManager pManager)
   {
     pManager.AddIntegerParameter("Counter", "C", "", GH_ParamAccess.item);
   }

   protected override void SolveInstance(IGH_DataAccess DA)
   {
     int maxCounter = 10;
     int interval = 500;
     //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 interval)) return;
     if (!DA.GetData(3, ref maxCounter)) return;

     if (timer == null)
     {
       timer = new System.Windows.Forms.Timer();
       timer.Interval = interval;
       timer.Tick += UpdateSolution;
     }

     if (reset)
     {
       Reset();
       timer.Interval = interval;
     }

     if (run && !timer.Enabled)
       Start();
     else if (!run || timer.Enabled && maxCounter != 0 && counter >= maxCounter)
       Stop();

     DA.SetData(0, counter);
   }

   public override Guid ComponentGuid
   {
     get { return new Guid("29166f33-d4c0-4e40-9f9a-556b2d251760"); }
   }
  }
}

Thankyou

The problem with a windows forms timer is that it wants to belong to a UI context, such as a form or control. The other problem with using timers is that they may fire too often for solutions to complete. There’s a scheduling mechanism for GH_Documents which internally runs a timer, you may be able to use that instead?

There’s some issues I’m getting with this code too, but have a look at it:

using System;
using Grasshopper.Kernel;

namespace TimerTest
{
  public class MeshIterativeMove : GH_Component
  {
    public MeshIterativeMove()
      : base("Timer Example",
        "TimEx",
        "Run an adjustable timer",
        "Test", "Test")
    {
      Counter = 0;
      Maximum = 0;
      Running = false;
      Schedule = DateTime.MaxValue;
    }

    private int Counter { get; set; }
    private int Maximum { get; set; }
    private bool Running { get; set; }
    private DateTime Schedule { get; set; }

    protected override void RegisterInputParams(GH_Component.GH_InputParamManager pManager)
    {
      pManager.AddBooleanParameter("Run", "Run", "Run the timer", GH_ParamAccess.item, false);
      pManager.AddBooleanParameter("Reset", "Rst", "Reset counter to 0", GH_ParamAccess.item, false);
      pManager.AddIntegerParameter("Interval", "Int", "timer interval in milliseconds", GH_ParamAccess.item, 500);
      pManager.AddIntegerParameter("Maximum", "Max", "Maximum count", GH_ParamAccess.item, 100);
    }
    protected override void RegisterOutputParams(GH_Component.GH_OutputParamManager pManager)
    {
      pManager.AddIntegerParameter("Counter", "C", "", GH_ParamAccess.item);
    }

    protected override void SolveInstance(IGH_DataAccess access)
    {
      bool running = false;
      bool reset = false;
      int maximum = 10;
      int interval = 500;

      if (!access.GetData(0, ref running)) return;
      if (!access.GetData(1, ref reset)) return;
      if (!access.GetData(2, ref interval)) return;
      if (!access.GetData(3, ref maximum)) return;

      if (interval < 10)
      {
        interval = 10;
        AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, "Timer intervals must be 10 milliseconds or more.");
      }

      Running = running;
      Maximum = Math.Max(0, maximum);
      if (reset) Counter = 0;

      if (running)
        if (Counter <= Maximum)
        {
          Schedule = DateTime.UtcNow + TimeSpan.FromMilliseconds(interval);
          OnPingDocument()?.ScheduleSolution(interval, Callback);
        }

      access.SetData(0, Math.Min(Counter, Maximum));

      Counter++;
    }

    private void Callback(GH_Document doc)
    {
      // We've exceeded the maximum.
      // No further solutions from this object.
      if (Counter >= Maximum)
        return;

      // This callback *always* happens if we've scheduled a solution and
      // a new solution runs. Even if that new solution is much earlier than
      // the one we requested. If that's the case, i.e. if this callback is
      // called much too early, we need to schedule a new solution.
      var now = DateTime.UtcNow;
      if (now < Schedule)
      {
        // Yup, too early.
        var newDelay = Schedule - now;
        doc.ScheduleSolution((int)newDelay.TotalMilliseconds, Callback);
      }
      else
      {
        // Ok, this callback is close to or over our requested solution.
        ExpireSolution(false);
      }
    }

    public override Guid ComponentGuid
    {
      get { return new Guid("29166f33-d4c0-4e40-9f9a-556b2d251760"); }
    }
  }
}

I got one from @Dani_Abalde where he uses events to get that behaviour. since events are able to change values in realtime. but the logic is pretty mind breaking and I find it hard to get it to stop at the max number of iterations. Also I’m not very familiar with the usage of events so I find it hard to implement.

Here is it:

using System;
using System.Collections.Generic;
using System.Windows.Forms;
using Grasshopper.Kernel;
using Rhino.Geometry;

namespace SurfaceTrails2.Mesh
{
public class MeshIterativeMove : GH_Component
{
private GH_Document ghDocument;
public Timer timer = new Timer();
int maxCounter, interval;
public int counter;
private bool reset, run;

    void documentSolutionEnd(object sender, GH_SolutionEventArgs e)
    {
        ghDocument.SolutionEnd -= documentSolutionEnd;
        timer.Interval = interval;
        timer.Tick += timerTick;
        timer.Start();
    }
    void timerTick(object sender, EventArgs e)
    {
        timer.Tick -= timerTick;
        timer.Stop();
        ghDocument.NewSolution(true);
    }

    public MeshIterativeMove()
      : base("Mesh Iterative move Attractor", "MeshIterativeMove",
          "Moves mesh's closest vertices to attractor point iteratively",
          "YFAtools", "Mesh")
    {
    }
    protected override void RegisterInputParams(GH_InputParamManager pManager)
    {
        pManager.AddBooleanParameter("Run", "Run", "run the Component", GH_ParamAccess.item, false);
        pManager.AddBooleanParameter("Reset", "Rst", "Reset timer to 0", GH_ParamAccess.item, false);
        pManager.AddIntegerParameter("TimeInterval", "Int", "The time interval between iteration in milliseconds",GH_ParamAccess.item, 500);
        pManager.AddIntegerParameter("MaximumIterations", "Max", "Maximum number of Iterations", GH_ParamAccess.item, 0);
    }
    protected override void RegisterOutputParams(GH_OutputParamManager pManager)
    {
        pManager.AddIntegerParameter("Counter", "C", "", GH_ParamAccess.item);
    }
    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 interval)) return;
        if (!DA.GetData(3, ref maxCounter)) return;

        if (reset)
            counter = 0;

        if (run && !timer.Enabled)
        {
            timer.Start();
            ghDocument = OnPingDocument();
            ghDocument.SolutionEnd += documentSolutionEnd;
        }
        else if (!run || timer.Enabled && maxCounter != 0 && counter >= maxCounter)
            timer.Stop();

        counter++;
        DA.SetData(0, counter);
    }
    protected override System.Drawing.Bitmap Icon
    {
        get
        {
            //You can add image files to your project resources and access them like this:
            // return Resources.IconForThisComponent;
            return null;
        }
    }
    public override Guid ComponentGuid
    {
        get { return new Guid("29166f33-d4c0-4e40-9f9a-556b2d251760"); }
    }
}

}

can that be achieved with DateTime ??

I don’t understand what that means.

Events in C# are more complicated than they needed to be, although that complexity is mostly part of the declaration, not the consumption of events.

An event is just a mechanism for one function to call a bunch of other funtions. Every event can have any number of ‘handlers’ attached to it. These handler methods must have the correct signature so they can be invoked.

You attach a handler to an event using += operator, and you detach it using the -= operator.

The only thing you have to always keep in mind with event handlers, is that they are called on the thread of the event itself. So for example if you’re handling System.Threading.Timer.Tick, the attached handler will be called on whatever thread the event fires, in all likelihood not the UI thread.