Grasshopper

algorithmic modeling for Rhino

My goal is to find a good solution to the following problems:

  1. A way to start a new solution of a different thread (E.g if events start coming in from a USB device, a webserver, a form or anything that does not run inside the Grasshopper thread).

  2. A way for components to subscribe to a "New solution at the earliest conveinience the next time a solution is finished". This may include a small pause for grasshopper to redraw the canvas; or a pause to prevent locked-up scenarios and unescapable stuff.

  3. Multiple components should be able to subscribe to this, and they should be expired at the same time. (Right now I'm using solution #2: When having a lot of input this causes a racing condition, where one of the two components will be expired, causing a "turn based scenario".

  4. A no-queue solution: Multiple calls during a solution should be collected into one batch of components / params that should be expired.

This is the code I've tried

1: Adding a new handler at the end of a solution

GH_Document.SolutionEndEventHandler handle = null;
handle = delegate (Object sender, GH_SolutionEventArgs e) {
// first get rid of our handler again
GrasshopperDocument.SolutionEnd -= handle;
if (GH_Document.IsEscapeKeyDown())
return;
try {
//
ExpireSolution(true);
} catch (Exception ex)
{
RhinoApp.WriteLine("Exception in SolutionEnd event: {0}", ex.ToString());
}
};

2: The Ghowl UDP way:

if (_doc.SolutionState != GH_ProcessStep.Process && _udpClient != null && !_askingNewSolution) {
GH_InstanceServer.DocumentEditor.BeginInvoke((Action)delegate() {
if (_doc.SolutionState != GH_ProcessStep.Process)
{
_askingNewSolution = true;
this.ExpireSolution(true);
_askingNewSolution = false;
}
});
}

My use cases are:

  1. A USB Hid listener (Should be able to handle e.g. two gamepads at the same time) sending commands to a component. Events can arrive at high speeds; and should be able to know when it's save to actually request a new solution

  2. A form that sends keystrokes or commands to (multiple) components on the canvas (one per device).

  3. An embedded webserver

  4. Looping (like: Hoopsnake, anemone, etc. Sending data back to one or more parameters on the canvas)

For most of these problems I've got a solution where one of these components is working. But most of the time combining them results into misery.

These are my questions:

  1. What does ScheduleSolution() do? I've tried to use it - but it's behaviour seems erratic. Most of the expirations arrive quite laggy.

  2. What is the difference between ExpireSolution(true) and ExpireSolution(false)? When is it safe to call either of them?

  3. How do I determine if it's safe to request a new solution?

  4. Any thoughts on a generic way to solve this (Rather than each component subscribing to events, a way to subscribe to an expiration at the next earliest conveinience?)

Views: 427

Reply to This

Replies to This Discussion

ScheduleSolution should be the preferred method. It is conceivable it doesn't do what you need, in which case you'll need to find a different solution, but always start with ScheduleSolution. The way it works is as follows:

  • The GH_Document has a list of objects that have scheduled a solution (or rather, it maintains a list of callback delegates those objects have registered).
  • It also contains a TimeSpan field, which remembers the shortest schedule.
  • If the ScheduleSolution method is called during a solution, the timer won't be started until the solution finishes. So the schedule doesn't control how often the solutions will occur, it controls the delay between solutions.

Let's imagine the following scenario (with insanely scaled up time spans):

  1. At noon exactly, a new solution starts. It doesn't matter what triggered it.
  2. While the solution is still running at 12:01, a component (A) schedules a new solution 15 minutes later. This component registers a callback delegate along with the schedule.
  3. While the solution is still running at 12:02, another component (B) schedules a solution with a 5 minute delay. Since 5 minutes is less than 15 minutes, the document forgets about the 15 minute schedule and instead switches to a 5 minute schedule. (B) does not register a callback.
  4. While the solution is still running at 12:03, a third component (C) schedules a solution with a 10 minute delay. 10 minutes is further into the future than 5 minutes, so the document does not accept this new schedule. (C) does however register a callback.
  5. At 12:05, the solution finishes. The SolutionEnd event is raised, viewports and canvasses are redrawn.
  6. Also at 12:05, the document starts a timer that will fire an event 5 minutes from now, at 12:10.
  7. Nothing happens in this interval, and at 12:10 the schedule timer fires.
  8. The document notices it has a list of two callbacks (registered to A and C respectively), so it invokes them. This allows (A) and (C) to perform some sort of preparation. The most common action is to expire the component that gets the callback, so it'll get included in the imminent solution.
  9. Once the document has invoked all schedule callbacks, it starts a new solution.

Note that (A) and (C) got called back way earlier than they requested.  They scheduled solutions for 15 and 10 minutes respectively, but instead got the call only 5 minutes in. They can either play ball and accept the new schedule, or they can choose to not expire themselves and instead schedule a new solution for the future.

If at point 7 instead of nothing happening, a new solution was triggered by some other event (user dragging a slider or changing a wire), all the callbacks are still handled, but now even earlier than they expected.

-----

You can always schedule a solution, which is what makes this solution more flexible than other approaches. It doesn't matter that a solution is already running. It doesn't matter that the schedule comes from another thread*.

On the other hand, schedules are annoying because the time you request is not necessarily the time you get.

Also, if you have 50 components that all want to schedule, you must pick a delay big enough so that they all manage to register their callbacks before the new solution starts. This may be tricky.

* I'm actually not 100% sure about the threadsafety, it could be that there's bugs under rare conditions.

  1. What is the difference between ExpireSolution(true) and ExpireSolution(false)? When is it safe to call either of them?

ExpireSolution expires the object it is called on, including all dependent objects. If the argument is false, there will not be a new solution right after the expiration. If the argument is true, there will be a new solution right away.

Basically, use ExpireSolution(false) if you intend to do more stuff before the next solution starts. So if you want to expire 12 components, you call ExpireSolution(false); on them all, then when you're done you call GH_Document.NewSolution(false);

How do I determine if it's safe to request a new solution?

If it's scheduling, it's always safe.

If you're going to call GH_Document.NewSolution(bool) yourself, or ExpireSolution(bool) on any object in the document, then you need to check the GH_Document.SolutionState. If it's GH_ProcessStep.Process, then you are not allowed to start new solution.

Any thoughts on a generic way to solve this (Rather than each component subscribing to events, a way to subscribe to an expiration at the next earliest conveinience?)

I need to think about this some more...

Hi david,

I'll give ScheduleSolution another try, and figure out why it did not work before; it seems to be a rather complete solution to my questions (thanks!). Am I correct in assuming that GH_Document.NewSolution() will be called after the Schedule solution executed all callbacks?

Thanks a lot for the explanation!

Ok, small follow up of lessons learned:

Mistake #1:

Not treating Schedule Solution as an event. During the solution I called Schedule solution multiple times for the same component. This seemed to cause the lock-up scenario's.

Mistake #2:

Calling ExpireSolution(true) inside the callback of schedule solution, this seems a recipie for instant lock-up scenario's.

This seems to work pretty well:

        private bool _askingNewSolution;

        public void ScheduleSolution(int miliseconds)
        {
            // only ask once.
            if (_askingNewSolution)
            {
                return;
            }
            _askingNewSolution = true;
            OnPingDocument().ScheduleSolution(10, doc =>
            {
                ExpireSolution(false);
                _askingNewSolution = false;
            });
        }

Thanks a lot for the insight in solving this!

Do you really need the private bool there? As long as you don't trigger new solution from your callback, it should be fine. Even if you end up having multiple callbacks it shouldn't matter, as ExpireSolution(false) only does something the first time. The second time you call it the object is already expired and nothing happens. It takes a successful solution to make the object expirable again.

Ok, it could be that my mistakes are limited to #2. I'll have a good use case on friday to test it. (A controller that send out events every x miliseconds). I was not sure how expensive the ExpireSolution(false) call would be.

[Edit] My main line of though was: I don't trust the speed at which the controllers events (and thus requests for a new solution) are coming in. Nor will I know how fast or slow the entire solution will be. If e.g. 1000 requests for a new solution arrive in the time that it takes grasshopper to calculate one solution; I wouldn't want 1000 callbacks to be executed. Hence the boolean check to only add one callback to expire the component.

On an related note: Is it possible that ScheduleSolution(0, callback) in a loop will crash rhino in a die-hard fashion? (For a test case: change the timeout on line 129 in the C# component in the attached file.)

Attachments:

Oh incidentally if you're calling methods from a thread that isn't the UI thread, you may have to invoke them properly. Normally you invoke a method on the UI thread by calling the Invoke method on a control or form. We've also put an Invoke method in RhinoCommon to make things easier.

Good to know, thanks for the heads up. I'm still a bit puzzled when would be a good use case to use Invoke? Is code running in a grasshopper solution for example in a different thread than the UI thread?

Yes.

RSS

Translate

Search Grasshopper

Photos

  • Add Photos
  • View All

© 2017   Created by Scott Davidson.   Powered by

Badges  |  Report an Issue  |  Terms of Service