Main

7.2 Async Components from Scratch - Advanced Development in Grasshopper

In this video, I explain how to implement asynchronous operations in Grasshopper from scratch (no templates), so that you can execute costly operations without blocking your entire software. I will also use this to explain the update cycle of Grasshopper components. 📝Victor Lin's notes on the GH component execution cycle: https://lin-ycv.github.io/notes/Grasshopper/Execution%20flow/ 📝Course playlist: https://www.youtube.com/playlist?list=PLx3k0RGeXZ_yZgg-f2k7fO3WxBQ0zLCeU 💻Code: https://github.com/ParametricCamp/TutorialFiles/tree/master/Advanced_Development_Grasshopper Join us: 💬 https://discord.gg/D2S8GqMfWs 📺 https://youtube.com/ParametricCamp 📷https://instagram.com/ParametricCamp 🐦https://twitter.com/ParametricCamp 💻https://github.com/ParametricCamp 📷🕺https://instagram.com/garciadelcastillo 🐦🕺https://twitter.com/garciadelcast

ParametricCamp

9 months ago

Hello, this is Jose Luis here at ParametricCamp. And welcome to another video in our advanced development playlist, where I would like to show you now, if you remember from the last video in this series, I was teaching you how to create a synchronous components components that do not block Grasshopper if they have a very heavy operation. And I showed you how to do that using a template for a synchronous components that the folks at speckle systems had open source for all of us, which is very, ve
ry nice. However, I was actually interested in digging down what that template is doing. And in this video, what I would like to teach you is how to actually do this very same trick, the idea of a component that only updates after a long heavy asynchronous operation has been completed, I would like to teach you how to do that from scratch. And it will be a very interesting excuse to also break down and understand a little better how the Grasshopper engine deals with update cycles in Grasshopper
components. So demonstration, what we're going to do is that here on the top, I have, I'm actually going to do this, on the left hand side, I have the specter component that does a very heavy for loop operation. And on the bottom, I have the one that I have implemented raw by hand without the template. And you can see that if I now navigate through a bunch of different input values, what you can see is that this component is giving me in an a synchronous way in a thread way is giving me all of t
hose results, but it's not looking Grasshopper at all. Whereas the SPECO one is still calculating, but if I am changing those values, while speckle is still computing, it will only give me the last result. Okay, so it's a little bit of a difference between the two approaches that we have done. And again, at any rate, the interest of this video is both doing this by hand, but at the same time, trying to understand a little better the general architecture of update cycles in Grasshopper. Okay, so
if you have not seen the previous video where we implemented speckle, there will be a card popping up somewhere in the in the corner over there. And otherwise, just check look for that video on the playlist advanced development in Grasshopper. Alright, let's dive in. Before we start writing any code, of course, what I would like to do is I would like to first take some time to break down in a very superficial way, how the update and the execution cycle of Grasshopper works in more or less, right
. And in order to do that, we need to understand that when we change the data in any Grasshopper definition, there is a bunch of things, a bunch of processes that are happening in the background, that take care of updating the solutions in the Grasshopper components, and then broadcasting a chain of events down the graph three of Grasshopper components, so that anything that depends on that obsolete data gets updated properly. All right. So I've drawn this fantastic Grasshopper definition here.
And I wanted to use this to explain more or less the way this works from a code standpoint, right. And the idea is that if let's say we have a definition that starts with a slider has a component, and then it has two other components that depend on the first one, what would happen if I were to change the slider is that there would be a change in the data that is flowing from the slider to the component that we are targeting, right? What happens in that component is that Grasshopper as a computat
ional engine, if you will, what it will do is that the first thing that it will do is that it will flag that component as expired. By expired, we mean that the data that that Grasshopper component contains is not valid anymore, because since it has detected that the input has changed, then it knows that whatever data was there is not valid is obsolete any routes to recompute that data. But the key here to understand is that that expiration happens first is the first thing that happens in the com
ponent, and that expiration actually broadcasts to components that are leaving downstream after the component that has expired. So what that means is that there's like a chain of expired, expired, expired expired, and then all those components are flagged as obsolete or dirty or however you want to call them and then they are scheduled for having their data recomputing Okay, and if you've been following these playlists you know that recomputing the data is typically done using the function that
lives in Grasshopper components that is called sold instance. So instance is called after the component has been flagged as expired. And then what solve instance does is it takes the new input, it looks at the input, whatever it is this new stuff, it takes the input, it performs our calculation, and then assigns values to the outputs. And then those values that are assigned to the output, will then flows downstream into the other components, which are also expired, and are also flagged to take n
ew inputs, recompute them, create new outputs, etc, etc, right. So this is roughly how things work. When we want to achieve though, it's kind of a different behavior, one that Grasshopper natively does not, does not provide, which is one where if we assume that the big component, the weather we're working with at the center, is going to take a long time to execute. And we don't want the component we don't want Grasshopper to freeze, we want things to keep flowing. But we don't want the component
s after this one to expire or to change or to trigger any update, then what we need to do is we need to hack this process a little bit, so that we control the expiration and the solving of instances in a way that suits this asynchronous or this like deferred update of the solution that we want to achieve. So how would this work, I'm going to try to now make a good case as explaining this, we're going to still accept a change from upstream, we're still going to detect that the input has changed,
right. But when that happens, what we want is, first of all, we don't want to expire this component right away. So we don't want to do that. Okay, we don't want to do that, because what we don't want is for the rest of the components downstream to be also expired and to be waiting for this solution that is going to take some time to execute. So what we're going to do is we're going to detect the change of the input, and we're going to prevent this component from expiring, this component is not g
oing to flag anything downstream, as obsolete as needing more calculation now. And then what we're going to do is we're going to still solve the component, we're going to trigger an update a calculation, we will probably do that calculation a synchronously, so it was somewhere on a thread, somewhere in the background is going to calculate and whenever this calculation that takes a long time gets done, what we're going to do is we're going to ask this component to trigger again, we're going to ac
tually expire the solution so that the whole process starts again. And what will happen is that and we're going to store the data that we have calculated, we're going to store that data somewhere, especially memory, so that after that long calculation has been executed, and the data is available store somewhere in the component, what we're going to do is we're going to expire the solution so that everything triggers and everything starts over again. And when it starts over again, this component
at this point, we actually want it to expire, we will want that expiration, so that so that the data that we have saved, we now broadcast that data all the way downstream and components that depend on this component get updated in that moment. This was a little too much to say. But in a nutshell, whenever we change data in the input, what we want is to have the component not trigger any updates downstream, perform some calculation in the background. And when that calculation is done, then trigge
r a new update. So that we start over with the whole cycle again, in a normal way, we expire this solution, these things are scheduled for updating again, and we broadcast downstream all that data that we calculated in some kind of offline task or have a background thread, and then we pass that data downstream. Okay. The way we're going to do this, again, is not in a way that is natural to Grasshopper, it is going to require like a little bit of hacking of the behavior of the component. I am I b
elieve Grasshopper, too may have some of this logic embedded and you will be able to do this. And naturally in Grasshopper, too. So if you're watching this from the future, and Grasshopper two is available, please maybe ignore all of this and find how this is properly done in Grasshopper two, okay. But in in the meantime, this is one trick that we can use. And as I said previously, in the introduction, this technique that I'm doing is pretty much the technique that speckle The template that we s
aw in the previous video is actually using under the hood. Alright, so if anything, this video can be helpful for you to understand a little more of the update cycle in the execution cycle of Grasshopper components. Alright, let's take a look at how to actually write code that does this mess that I just proposed to you. Alright, let's, let's take a look at that. So if you remember our previous video, we had this component that basically did this very long for loop with the speckled template. Alr
ight, so what I'm going to do is I'm going to now go here, add a new item, and I'm going to create an empty Grasshopper component that I'm going to call a seeing for loop from opponent without the specs part, alright. And I'm going to put it here side by side, because I'm going to copy and paste a couple of things. So for example, here, this is going to be called a synchronous for loop is going to be part a synchronous for loop, a synchronous for loop, the category is going to be ParametricCamp.
And the subcarrier is going to be asynchronous. Alright, so I don't need a base worker, because I'm going to do this from scratch. So I'm going to copy and paste an input for iterations, I'm going to copy and paste and output as a message, right. And then I have salt instance, I'm going to add these placeholder icon that we've been working with, just for the sake of it, alright. And then we do have the salt instance, this whole instance. So I think I'm going to go fullscreen here now. And then
the first thing, if you remember, the first thing that I want to do is, I think the first way that I'm going to prototype this is I'm going to make sure that I create a component that no matter whatever changes are happening, no matter whatever changes are happening, coming from the input, it doesn't expire anything downstream. Okay, and then after that, we will figure out how to connect everything together. So the first thing I'm going to do is, I'm going to create, I'm going to go here, and I'
m going to override a, I'm going to override a, a function that exists in Grasshopper components that is called expired downstream objects. This is the function that gets called whenever a component is expired, and then the handles what it does, if I leave it like this, what's going to happen is that whenever this component gets expired, is going to call the typical behavior of Grasshopper components when they get expired. So what I would like to do is, first of all, I would like to stop that be
havior. So what I would like to sit and do is maybe I can just cancel it. Or if I want to be a bit better, what I can do is I can create a private Boolean flag here, for example, shoot, expire, and I'm going to call that false. And then I'm going to use this variable to control if this component at any given time creates expirations or it doesn't. So if should expire is true, then please expire the component otherwise, the component is going to prevent any expiration downstream. All right. So ex
pired downstream, overriding it. Let's see if this works. So I'm going to do I'm going to open Grasshopper, now I'm going to load that template file that we have with a bunch of tests, etc. Right? And I'm going to try the component first, I'm going to ignore the recovery file. I don't know what that is. All right, PCAP components. And it takes some time to load because I found that definition is quite heavy at this point. All right. And you see this speckle one that we had before. Alright, so I'
m going to copy here, I'm going to go here. Now I have the specular component. And I have asynchronous for all right, I'm going to plug this in here. Alright. And then I'm going to connect this. Well, you can see is that this is basically we don't see anything here. But we don't really know if this component has been updated or not. So a good way to for doing that is I'm going to use the recorder. Alright, I'm going to plug it in here. And if the component were to update, if that component was u
pdating, then the recorder will be giving me No no, no no. So for example, if I do this, you can see that the component is not adding empty solutions, because this component is not triggering updates. On the contrary, for example, you see if I were to plug in a the slider right away, you see that as I change values, I keep adding adding more and more solution because this component is been updated. Alright. So I think the prevention of the exploration is already working. Alright, so Then how can
we now do that offline background calculation that we wanted to do? Let's take a look at that. For starters, we're going to do what we typically do, which is we're going to first gather the inputs. So we're going to define a variable called iterations, we start at zero, and we're going to say, if we didn't get anything from the input at location zero, and that we are storing at iterations, then stop executing this component. Alright, so we've seen this pattern in previous videos in this playlis
t, right? So and then what we would like to do is now we would like to perform this a synchronous, large for loop, then we're going to do that is we're actually going to call some other function that is going to do the a synchronous calculation. So I'm going to say, for example, I'm going to create a private function that is going to return nothing. And I'm going to call this a synchronous for loop. And I'm going to take an amount of iterations as an input. Alright. So when want to do is, I'm go
ing to launch in this function, I'm going to launch a background thread. And that background thread is going to you do some kind of heavy calculation in C Sharp, we haven't seen this yet, I think in previous videos, but what you can do is you can very easily start a task that goes on a thread in the background, by just create saying, I'm going to run a function in the background using a task manager, right? What I can do is I can define a function here on the fly, right? So what I could do is I
can say, I'm going to define a, an anonymous function. And that I can do by saying this anonymous function that I'm defined here on the fly is going to take no data as an input, and is going to execute this code that I have here. And the code is going to be something like this, for example, how many iterations I'm going to run is going to be zero, and then for int i equals zero, i is less than eight, Id i plus plus i plus plus. And then what we could do here is we could say iterations equals to
i e, r n, just for the sake of making sure that we see these taken time, I'm going to do some kind of expensive calculation here. All right, so I'm going to say, math, square root of math of the square root of math of the square root. And I could probably write a for loop for doing this. But I'm just being lazy at this point, you know, I'm going to do four nested square roots for this particular value, which I'm actually not going to use at all, just for the sake of making the this calculation a
little more expensive. All right, beautiful. So then, what are we going to do were, what I would like to do is somehow store the result of this calculation. Again, what I'm doing this here is a little abstract. But you can imagine that this could be something very heavy, are we going to be doing an asynchronous call, we will be doing a post request a quick get request from the internet. And actually, there will be a video somewhere in the channel where I'm using this to make a post request to a
n API service. If the videos already online, there will be a card popping up to this video somewhere. All right. And then what I would like to do then is I would like to store the result of this calculation somewhere. But because it is a for loop that is happening asynchronously. And that is kind of detached from the main instance here. So I could call this from a sink, I could call this a synchronous for loop. And I want this number of iterations correct. But because this is going to be running
on a separate thread, what I cannot do is I cannot return a value that I can store here. So the way to do this is actually not very complicated, which is what I can do is I can create a variable in my component that is going to be private, but it's going to be a scoped inside of the class that represents the component and therefore it's going to be accessible to anything that leaves execute or if it's accessible from within this class. Alright, so let's say that I say I'm going to create a some
thing that is called a message a private variable that is called a message and I am underscoring this name just because in C sharp it is a convention to add an underscore at the beginning of any variable that is private and is not meant to be shared or read by external processes or external classes right. And then after the iterations are done, what I can do is I can say, message is going to be equal to completed and then iteration Shen's plus the word the actual word iterations. Okay? Beautiful
. All right. So how is this going to work? Let me just run this code, see if this works in the definition, etc, etc. So I'm going to do is going to open Grasshopper, okay. And something broke disconnected context, I don't know what that means. But Grasshopper actually did start. And maybe it has something to do with Rhino and Grasshopper I have Grasshopper on my other screen, yes. So if I change this, this works. This, I'm assuming is also working. But I'm not getting any feedback because rememb
er, we had canceled the expiration of this component no matter what changes we do to the inputs, this component is not going to let any of those changes go through. And it's also not going to create any output because we haven't created that anywhere. All right. So here's where the tricky part comes. And I want you if you're paying attention, I want you to pay perhaps a little more attention to this, we have completed the first part, which is we change the input, we prevent the expiration. So th
ere's no expiration, we start an instance. So we trigger solving the instance we do this heavy operation in the background. And we have some data now, which is the result of that operation, okay, that we have stored as a private variable in our component. Now the trick is, what I want is, once I have that data computed, what I want to do is I want to trigger a new solution in the component so that the component now restarts if you will, right. And then upon that restart, what I want us to change
that behavior that I just implemented, I want exploration now to actually go through so that everything downstream gets triggered and updated. And I want that data that I calculated before, to go downstream to be broadcasted to anything that goes behind in the update chain. How am I going to do that is going to be super easy, I'm actually going to use that lag, that Boolean flag that we created before, to signal on and off exploration and solution. Alright, let me show you how we're going to do
that. If you remember, we were not expiring anything downstream, because we were blocking that expiration with this one variable called should expire, which right now we initialized to false and it's always false. And that's why we're not updating anything downstream. Alright. So how about this? Imagine we have our asynchronous task, right? This is the one that gets thrown into the background, hat running runs in its own thread, it's doing its thing. And then at some point, it finishes doing th
is very expensive calculation, right? When that happens, this is where we store that data that we hope to pass downstream. What we can do after that is we can say, You know what, what if, once we've calculated this, we change should expire from false, we change that to true, that in itself is not really going to do anything. However, what we can do is like we can do that, but at the same time, we can force the component that we're apt to update itself. Alright. There are many ways to do that, fo
r example, the typical way is to use expire solution, and just say expire solution true, and then trigger that. However, I'm not going to show you here, but this will not work on the on this task, which has been threaded, because since it's been threaded, this expires solution does not have a reference to the thread that actually started the whole issue, and therefore is going to create a problem. Alright. So instead of doing this, the other the recommended method, especially when you do a synch
ronous stuff, and you want to do different threads, would be to just make sure that you capture the thread that is running the Rhino UI and that within that thread, you call the expiration of the solution. The way that it's done in writing right now is by saying, you know, what, can we access the Rhino app? And within the Rhino app, can I please invoke the thread that is constrained the UI and inside of that thread? Can I run a particular function that I want to run? In this case you will be exp
ire solution. The problem is that I cannot write expire solution here. True, because this invoke on UI thread, the only thing that takes as parameters. So if we look at we open here, you can see that the only thing that it takes is this thing called and delegate. And delegate is basically a function that we define with a signature. And because the signature is constant, C sharp knows that it can call it with some input data. And that is going to return some kind of output data. All right, we hav
en't seen delegates. Also, I think in this channel yet, hopefully, we will do that at some point, if we've done it, there might be a card popping up in the corner. Otherwise, please be patient. And we'll record that video. But for the sake of time, what I'm going to do here is I'm going to define that delegate, I'm going to declare it on the fly, I'm going to say delegate, and the delegate is going to be something that is going to take this is going to be a function that is going to execute insi
de of itself is going to call expire solution, right. And then just something I need to cast this to a function that takes nothing as an input and returns nothing as an output. So that will be an action with no types in it. Alright, I know this might be a little weird to read or to understand. But hopefully, when I record delegates and action functions, I can explain that better again, maybe there's a card popping up, maybe there isn't yet. Sorry about that. All right, beautiful. What that will
do is that this will de facto, make the component that we are living in, up to date itself entirely again, so it will go again, and do and request an expiration, it will do a salt instance, it will do the whole thing. Okay. Because it will do both the exploration and solving the instance, we need to modify those two functions to make sure that they work the way we want. So expire downstream, after we invoke this expiration of the solution, because we have changed should expire to true at this po
int, because this is going to be true is going to expire downstream objects, which is what we want. Okay. So in that sense, we're good. However, here, if we go to solve instance, what may happen is that because of instance is going to be called again, what will happen is that we're going to capture the input again, we're going to go for another update, this is going to run again, this is going to be updated, again, there's going to be another exploration that we would enter this infinite cycle o
f exploration solutions education solution, and we don't want that. So we want to do is we want to say, because we know that solve instance, is going to be called a second time. We know that that second time that is going to be called is when we have flagged should expire to be true. What that means is that this is that we need to capture that and we need to say if should expire equals true, it means this is the second this the second time sold instance was invoked. Alright, if it is the second
time, this time, we don't want to do the calculation, we already did the calculation. And we the result of that calculation is in this variable called message. So what we want to do is, if it's a second time, no calculation is to be done, just set the data set the output to that message that we had already pre computed before, and that we have in a variable here. All right, just do that. And then make sure that you we stop expiring things that we go back to the initial state. So we don't want to
expire anything unless we perform a calculation and stop, stop running anything else in this component. Alright. If that happens, then the second time that showed instance is executed, this gets turned on, we output the result of the previous calculation. We set this back to false so that the next time we move the slider, we don't expire right away, and we stop executing. So we don't read the input. We don't read the input and we don't perform any other calculations. Let's see if this works. Ok
ay. Okay, and not getting any errors. It looks and Grasshopper is a little frozen because it's apparently I think that file is quite big at this point. All right. And you can see that already we have something we have one solution that was computed. Alright, so let me move this slider a bunch of so I'm going to go go through several values. And I want you to notice how the solutions are probably going to start showing up one at a time. So I'm going to do this and this, and this and this and this
. And you see up to 2222. Right? And if you actually notice, this was actually not blocking the threat. So it's not as low as this one. Maybe I should. But if I do this, you can see that I can still move and solutions were still being generated, and we're not getting any empty solutions. So I'm going to say, Hooray, it works. Which is great. Let me let's do a comparison with the spec approach. So how does spec go? So I'm going to clean up both. And I'm going to have this one here. And the second
one over here, let's, let's come, let's compare how both of them right now work. So I'm going to go through several solutions. All right, several numbers. And you can see that I'm getting all of them buffered one after the other, however, speckle it's even a little smarter. And he says, Well, you know what, if you're changing the value before I finish computing, then I'm not even going to bother, I'm just going to cancel what I was doing, and then go right away to the new solution, right. So th
is is a natural consequence of the way we have, we are not preventing those calculations from happening, we're not buffering anything. So what will happen is that any changes that we do will happen in the background will tend to will take time to execute, but they will all execute. So I guess this is a natural consequence. And depending on your use case, depending on whether if you want all the change in solutions to be computed, are only the one that is final and stable to be the one that goes
through, I think that will probably be up entirely to you. So if you want all of the solutions, you probably want to go for the row approach like we did here. Or if we want only the very last one, you probably want to use the speckle template for foreign let me actually use an actual proper specular component, you know, now much better. Okay. And then you probably want to go for that solution. All right. Beautiful. I know this may have been a little push. But we were basically trying to hack and
Grasshopper to do something that it doesn't want to do naturally, which is breaking the natural update cycle of components and explorations and downstreams. Alright, so do this with caution is not how Grasshopper wants to work, you might get you into trouble if you have many of these happiness the same time if you're launching too many threads in the background, etc, etc. And use the row approach or the speckled one, depending on your needs. And depending on how much you want to actually take c
are of all of this. Alright. Something that we haven't done in this video is what I actually like a lot about the speckle approach is that it has this very nice message that is giving us feedback about what is happening inside the component. So I may record after this video, another video where I show you how to display messages in Grasshopper components, which is actually a very simple thing to do. Alright, so let's go and record that video right now. And in the meantime, if you like this video
, you may want to consider liking the video subscribing to the channel saying hi giant joining this court, pinging me on Twitter, Instagram, whatever, whatever you want to go. All right. Thank you very much and see you in the next video. Bye bye

Comments

@thomaslindemann5655

Hi Jose Luis, Amazing videos, Thanks for this series! I am almost sad to come to the end

@davidsaylor4349

Just finished this video. Very timely, as I am looking for a solution to just type of problem. Thanks much.

@riccardoviola3509

Great series of videos! I haven't seen them properly yet so I don't know if you say so, but have you thought about posting a video with asynchronous animations? I was thinking, for example, of a robotic application or an assembly line

@avishekdas9837

You should make this illustrated grasshopper component mechanism as a video and we will keep it as a holy grail! :D