Tutorial: Unity & Multithreading

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
so what are we doing today we are finally going to address the sign identity about this before but I couldn't find one in my log a lot of people ask me a lot of the time about multi-threading in unity and multi-threading in general and there is a lot of misconceptions about multi-threading in general as well as multi-threading in unity in specific law some people say unity doesn't support multi-threading that's not true you can totally do multi-threading inside of unity but there are a variety of caveats that you've got to look at there's a lot of people this comes up a lot whenever we play Dwarf Fortress and we look at the performance losses that start to happen in the late game right you see your portrait should start to suffer FPS depth and like ah if only Tarn would just magically enable multi-threading all of our problems would go away it's like no actually that's an incredibly difficult problem to solve till you know certain multi-threading pathfinding doing different things like that because because the reason that we're going to talk about here it's very very easy as the worst I think offenders are the people who have you know just finished you know their their basic university courses or whatever and in an academic setting with a very specific scenario it is actually very easy to implement emilie threading in fact we're going to show some examples of it here and within you know this hour-long session it's actually quite easy to initiate multiple threads to do things the problem is that in a real life scenario in a complex piece of code that it becomes incredibly non-trivial to operate multiple threads that interact in in in any sort of any sort of way that is above just like the basic sort of minimum interactions that you can manage and in fact it's very easy to generate multi-threaded code that is slower than single threaded code in you know more complex examples that there needs to be a lot of blocking if you end up with not necessary the race conditions all the race conditions can happen but you know a lot of like waiting on other threads you can end up with a situation that is just slower than a single-threaded program which is kind of the opposite of the intention but we're going to talk about a few things and some good use cases over here we're also going to talk about core routines which is sort of the another way to it's a fake multi-threading that solved some problems but then creates others and I really heat the the the syntax for a co-routines which is why I never use them they feel like they feel crummy for some reason but that might not be entirely fair Charlsie with the one year sub Hey hi Charles II good to see ya thank you very much for the one year the one your Twitter ursery over there okay I wouldn't have to sort of yes we're going to move on we're organizing some questions at the end but I want to get to the core work done first and then we can answer some issues it's on what we've done so I set up a basic project here I will be uploading this to the the website later on in this video will be uploaded to the up tub as well so I've got this example script here and what we've got is I've got a simple little script that has I mean it has a start function it has an update function you know standard unity stuff and then it has this one function called flow jump this thing is a job that takes a while what it what what is it basically it's just a single leaf it's looping a thousand times and each step in this loop is going to take two seconds to process this where I know it says thread there but we have none multi-threading this is just a way to cause the current threat every program exists in a thread a thread is a is a single sequence of execution which is normally executed linearly or well I mean yeah it is this happens when this happens in this happen in a multi-threaded environment multiple threads are running simultaneously and you don't necessarily know what order things are happening in because especially on an actual computer these days that have multiple CPUs with multiple cores maybe they can really legitimately be running simultaneously in there so anyway so we're we're looping a thousand times and inside of this we are we're just sleeping the current thread for two milliseconds ie we're simulating some sort of work some sort of series of equations that takes two milliseconds to execute which means this whole thing should take about two seconds in total because of overhead and a few different things like this it'll take a little bit longer than that the rest of it here this this is just starting a stopwatch to account the time then when we're done we stop the stopwatch we spit out the answer and then this is running that we said over here it actually is not going to do anything right now but basically when this function starts it sets is running is true when n it set is running to false and during our update function over here if is running a set to true I output job is running but this actually won't run here I'm going to ahead and play just to show that our default setup is working we're in it play nothing will happen until the job actually finishes and you have it here in the quote that you get the slow job that starts in the slow job that ends the reason we don't see this message come up first and then two seconds later this message come up is because your debug log commands they only get flushed to the log down here after a frame gets executed so what happens is we don't our frame time takes two seconds this is all running in one frame and so our one frame is taking over two seconds to execute takes about 2.5 seconds in practice to execute which means our frame rate our fps is less than 1 so obviously that would be very very bad this is a job that is incredibly important to do now if this is happening on boot right if this is something that that happens when you first load the game or first load a level it's not too bad you can say you know put up a little message that says loading please wait and you do this and that's fine but what this is something that has to happen while the game is running ok this can happen if you're trying to create some sort of seamless world where the player has just moved you know just walked far enough along that you have to load the next section of the world and you're trying to do it kind of seamlessly you don't want load screens right well how do you generate the next series of train you don't want to you don't want to hope the game you don't want to lag the game for two seconds while the next chunk is being created this is also going to happen variety objects this can happen for very complicated pathfinding operations now if you're having a path signing operation that takes two seconds or more that's probably a bad case but that's that doesn't tend to happen very often but it's very common for a task line operation take I mean think about if you're running your game if you want your game to run at even 30fps right that's what 33 milliseconds for frame no that's 300 milliseconds for front no hold on NASA's hard umm.yeah 1/30 yes is 0.03 3 so yes so if if you want to run at 30fps which ain't much yeah yeah then you can't take more than 33% milliseconds per frame if your path line operation takes longer than that by itself then you're not going to run at the speed that you want and what's worse is what if you're doing something like what if you're doing something like a city skylines game where you've got a thousand cars on the road at the same time and they all need to be passed on I mean they don't pass line every frame but every time a car spawns it's got to do a full pass line to find its destination what happens every time you replace a road then people have to recalculate the pass planning if that takes a few milliseconds every time then you're crushing your frame rate now there are some operations that you legitimately you can't do the next frame until this is done calculating but there are some things that you really could keep sort of rendering the game and you could keep accepting input and you could keep letting the player move around even though this other thing isn't calculated yet and there's where the idea comes up well what if we calculate this thing in the background in the mean time but how do we stop it from hanging they main thread which is what's happening over here we are hanging the game the game can't do anything for two full seconds while this is calculating first we're going to show co-routines co-routines do not involve multi-threading they're sort of a weird I know I don't know how to explain co-routines they're like you take a function and instead of just being a normal function you sort of bundle it into a special type of object and as the function runs it can return early and then keep executing next thread well go ahead and implement one and we'll we'll show how those now the weird thing about co-routines is that well they're not functions that return a void they have to be ie numerators they have to return an ie numerator and I mean they have to return something no matter what so at the end we do something like a return no otherwise we're going to get some sort of syntax there now we can call this function as is the slow job function can still be called exactly as is I think in the current state I think we're okay hit play yeah or exactly the same way we were before the slow job will run it'll take two seconds that's it the power of a KO routine is you can do something like this say every tick as it cycles through here we're going to say something like yield return null end execution of this function for one frame okay this lets the rest of the program continue to execute okay and then execution of this function will restart here on the next frame that's what this does although instead of calling the function directly you have to call it this way start co-routine like that there's a bunch of different ways to call the the co-routine you can do it by strings and things like that but I just and and it's different in in the unity script from c-sharp but this is this is fine so if we do this what's going to happen is slow job will execute like a normal function it will run perfectly like normal until it hits this line at which point the slow job function will freeze it will then return and we can actually confirm that by doing something like debug log start starting all right does our start function is starting and then over here we can say that the start function is done okay I assume that he hits this yield command this line ends and we're back over here but next frame at least I think I'm I think I'm telling you right pretty sure I'm right next frame next time update runs basically slow job will start executing again it will execute from here so it will restart this loop too you know the next step and then it'll hit this line again and then once again slow job will stop executing until the next frame so now the sudden we're in a situation this will take a thousand frames to execute which is not practical but let's go ahead and give this a run and confirm that this is working okay the compiler errors must be fixed hold on cannot return a value for me to Raiders that is true really we don't have to yield again at this point we can actually just dump out of the the code it's really weird because you're co-routines have to return have to have a return statement somewhere otherwise as an error you don't reckon you'd won at the end now that we've got this one here it will satisfy the compilation which is just one little bizarre thing there you know compilation errors we hit play there you go and what I'm going to do is I'm just going to stop the execution here and back up and we're going to see exactly what happens start that our start function sets starting then we get the line that says slow job doing a thousand things each taking two milliseconds then we see this line that says start done so what we've done at this point is we've printed out this line then in slow job we printed out this line then we we got to write here and as soon as we hit this return we end up there and then every frame we are looping through this some more and every frame is running is true so we're getting slow job is running slow job is running slow job is running over and over the problem with this is now instead of the job taking two seconds or two milliseconds to complete oh sorry taking two seconds right two milliseconds times 1,000 this was taking two seconds total time to complete now it's going to complete each one of these stalls for one frame so it's going to take 1,000 frames to complete is that going to buy me it's certainly not going to be less time it could be more time again if you're getting 30 frames per second then that's a thousand divided by 30 would be what 33 seconds then right 33 seconds now instead of taking 2 seconds to complete it's taking 33 seconds to complete but your programs not hanging but that's not ideal either because you need this thing to complete at some point right you can't if this is a path finding sort of situation you're not you mean you're willing to wait a little while for the path line to finish but are you willing to have your dude just sit you're you know your monster sit in place for 33 seconds before the path lining is calculated that's not doing either so then and here's the real issue with co-routines co-routines are cooperative multitasking which is something that we haven't heard of since fricken like since windows 95 was released previously to at least in the PC world previously to Windows 95 like in Windows 3.1 you can have multiple programs running but they were basically running like this they had to willingly give up their execution by basically doing this yield as of Windows 95 and as of you know Mac OS well maybe Mac OS X honestly although the the UNIX and Linux world have already had that screens that have pre-emptive multitasking where the OS allocates slices and pulls things away and the bonus to that is you can allocate the resources better because what's happening here is we're only running this once per frame but let's say again we're running at a fixed 30 fps okay we're only using a tiny slice of the the 33 milliseconds per frame we're allowed where are you only using 2 milliseconds per frame to calculate slow job maybe we could afford a lot more time and then you end up with doing weird things like ok that's no good what if we change this around we're okay we don't want to yield every frame we only want to yield every I don't know 10th frame or sorry not not a frame I mean instead of yielding for one frame every time we loop what if we yield for a frame only every tenth time we loop or every hundredth time we loop so then you're like okay if I in modulo you know a hundred is equal to zero so this is only when I is equal to zero 100 200 etc only on those so every 100 loops then we're going to yield but is that right should we yield more often should we yield less often I don't know while we're doing this stopwatch code what if we instead we look at how much elapsed time has gone by right and we only we wait every times like every time we've elapsed you know 15 milliseconds we will yield that's starting to be a lot of really stupid work to try to optimize this now you don't tend to need to do a lot of this stuff it's only going to be one or two functions somewhere in your entire code that ends up having to do this kind of nonsense but it still is exactly that nonsan you're having to try to guess about the right way because ultimately in your game if you're having to do some hard work you'd like to use 100% of the cpu so you're having to guess how to maybe end up with a hundred percent of your cpu and that question becomes a lot more complicated when you're not using a Frick's fixed frame rate if you're not you know fixed at 30 frames per second because you're not on a stupid console or something like that even being fixed at 60fps you know maybe you want variable frame rates because of whatever you know it's like well right now we can execute we can execute at 67 frames per second right now that's how much the computer can do it so it would be nice to use a hundred percent of our resources and with this kind of situation you can never use 100% resources efficiently because you can't really you have yes and you have to manually choose when to yield control over this function that's why I don't like co-routines I find them very confusing and kind of stupid so I don't tend to use them personally of course striving has its own issues that's what we're actually supposed to be talking about here so instead of using a codeine here instead we're going to use a thread so we're going to go and decoding this so I'm going to switch this back to avoid we are not going to yield anything whatsoever that's it so we're back to the function working the way that it was before and we're going to go back to calling it manually here for a second because I just want to make sure that it's still working so if we do this we should be back to no compilation errors hit play it should be one two and a bit for startup there we go and then it spits everything out okay so we're back to no threading no co-routines whatsoever so how do you actually set this as a separate thread the advantage of separate thread is this you are now offloading the job of balancing the work between your main thread which is what you know unity is mostly running in and your secondary thread your child thread which is where slow job is running it is worth noting unity is already multi-threaded with every sort of iteration of unity they've been sort of moving more and more tasks as possible to a background thread for example as far as I understand it the physics engine completely runs in a set or thread which is one of the reasons that you have this fixed update function fixed update is a function that runs basically every time the physics background thread is ready to start or restart or has just finished his previous educate execution depending on how you want to think about it okay and this is important the reason is fixed update runs is because this is when you synchronize between your main thread and the physics thread additionally to that unity is multi-threading a variety of graphical rendering stuff the graphics jobs is something that can be offset to separate threads because all and this is a theme that we're going to see as we go forward here whenever you multi-fit whenever you're creating another thread that other thread should be created for a specific task and it should be a task that can be isolated and do its own thing so the physics engine is running as a separate thing to resolve all like sort of the interconnections and how things are supposed to work and then when it's done it reports this new state of the universe back to unity it says okay I've done all the calculations this is where everything should be and it sends it back to unity and then unity before that on the next tick of the physics engine will run fixed update this is where you you manually say oh I want to add some velocity to object okay so you add velocity all these objects you set everything up in fixed update then unity packages will allow that up and sends it to the physic system and tells it to execute a thread and that's it that's why in your update okay over here because by default unity runs your physics kick 50 times a second okay if your frame rate isn't exactly the same right and update your frame rate might be higher you might grow at 60fps or more it could be lower you could be running at 30fps okay this update runs for every visual frame well if you're running at say 100 FPS that means that update runs twice for every fixed update which means it's entirely possible for you to do something to the physics engine here and then later on do something else like in a separate and you know the next time update loops and this first thing might not ever make it to the physics engine the way that you expect because they only really resynchronize here same thing with the graphics engine the way it works so well unity already does some multi-threading and you can totally add more threading so what we're going to do here is we're going to run slow job in a new thread and that's super easy to do we are simply going to create a new thread so we can create an object called fed thread my thread which is going to be equal to a new thread which requires you passed something now this can be a little confusing at a glance it wants a thread start object or a parametric pair met arised I'm sure that's not how you pronounce it thread start object over here that's a little deceptive because technically those two things are objects that are absolutely required if you're using the.net framework from C++ in c-sharp and Visual Basic you actually don't have to worry about explicitly creating those object you can but you can also just say something like we're doing a new thread and this new thread is going to run that function that's it you just pass the name of the function that you're going to run so this is our function here now notice there's no parentheses here if we did this it would execute slow job and then whatever slow job would return would then get passed to thread that's what we want what we're doing is we're passing a reference to the function remember that functions are effectively this name slow job write slow job is a variable that contains some function code when you add the parentheses at the end of the slow job variable name that's a shortcut to tell c-sharp execute the contents of variable slow jump that's what that is okay so here what we're doing is instead we are passing that variable to thread okay it's not executing yet and then what we're going to do is we're in the tell the thread goddamn typos thread thread dot start now run slow job in a new thread that will work so if we do this we should be able to run it now what's going to happen is this we're going to see start starting then we're going to see start done and somewhere maybe before this or maybe after this because the way the threading works we don't know we will see this line example script slow job doing a thousand things he's taking two milliseconds it might end up before this it might end up after this we actually don't know exactly how this get the threads will be scheduled maybe my thread will be scheduled right away and we'll happen to start running right before this line does we don't know I'm they're going to be running on literally separate CPUs on my computer so it's mostly a question of how long is it going to take for the CPU to be signal that it's got to be running this other thread at the same time so one or the other will happen first and then every frame of unities main thread we're going to see this but at some point this child thread is going to be done it's going to print out some stuff and it's going to it's going to exit but it's going to set is running to false at which point this will stop printing so let's give this a go and see what happens and the problem with threading is you can't say what order exactly things are going to happen in so I'm going to let this run there okay let's scroll back over here and see what happens we're going to stop running I suppose we can see start is starting start is done so we got we got this message here start done before we ever saw this message over here that's not always going to be the case not always going to pay you case you can't predict then slow job says it's doing a thousand things at this point is set is running to true so every frame of the program the main threads update routine right this thing over here is just saying slow job is running slow job is running and then at some point that gets to set to false because slow job has stopped running you can actually see it right over here oh actually this this is a quirk of how things got added to the the console log but again you can see very odd oh this is fantastic this is great look at this in the log it says slow job done that still says slow job is running why is that well it's because here we say slow job done and the next line we set is running is false but this printed out first and before is running to be set the false in our child thread in our main thread this executed again so it's like right away like oh well then I should make sure to set this to false first or something although the weird thing about this is then it's s is running to false but technically this thread is still running because it hasn't finished this yet what is right right is wrong we can talk about locks and all kinds of things in a second but these runs side by side now when this function ends then the thread has come to an end we can actually test that what we can do is we can set thread my thread to be a property to class here so instead of like creating a new variable here we can just set it there and we can check every every every tick we can do something so let's say this lets get rid of is running completely let me get rid of is running completely which means obviously we have to get rid of it here there's nothing to set the true and there's nothing to set the false over here and there's nothing to check here but instead I can say if my thread dot is live slowdown of is running so let's go and give this a go and see what happens I didn't hit play eisah could bank the console a lot bigger since there's literally nothing else going on we clear this hit play boom there we go so now same sort of thing happens start done slow jobs doing things is running as running is running is running is running is running at some point the thread prints this out and then is done there's nothing less to do in this function so it just exits and because that's the end of this thread it then exits now what you will see sometimes you will see some threads like with an infinite loop while true do something this thread now never exits on its own you can still kill it manually you could do something like a my thread dot abort there you go that will end the thread at that point you could do that to force it still there's four grounds four threads and background threads that you'll you may want to look into one will keep a program running one won't keep a program running this is not a good example I'm telling you right now that if you're watching this tutorial you are so not qualified to make advanced multi-threaded programs because first first of all I'm not qualified to make advanced multi-threading program your thread should do one job and finish it should be they should do one one set of pass line and go from point A to point B calculate that pass lining and then end if you are dealing with threads that are running continuously in the background you're going to do some more research on your own to you know do some amount of like management to those threads you're going to have to catch signals when the probe the user wants to exit the program then you know potentially kill threads off safely all kinds of different things like that so we're leave that there okay wow that's pretty good we're only we're only halfway through the hour we've dealt with the ability to start a thread over here now we're going to talk about the problem so now we have a multi-threaded program just like that we have a job that takes two plus seconds to run that is now running in a separate thread which means your main program never stalls never halts is always fully responsive to the to the user the updates are running normally and in my opinion this is a hell a lot nicer to work with than co-routines because we really didn't have to do anything like instead of calling start cold routine you just you thread boom you need it to like keep it right you could do um you can do it as a one-liner you could do new threads flow job dot start there you go now you have a one-liner that starts with red and that's it you forget about it it is kind of nice to keep a reference to it sometimes because you might you know you might want to do like my is my thread of life sure well that's very convenient so you can do so you can do that kind of crap so what are the problem okay this script is on a game object the only reason is running in unity the way that I've gone and done it is because it's on a game object I have my example game object over here and tell you what let's go and just for the sake of argument let's let's put a cube in it excellent so now it's a cube okay perfect another all zero zero yeah okay so this example script is now on this object that has some cube thing going on cool let's um let's make a little change to our script here let's have so in my update over here I'm going to do this this dot transform dot position or this star transform not translate okay I'm going to say vector three dot up x time comm okay so this piece of code here on every update what we're going to do is we're going to move the cube upward it's going to write move at a rate of one unity unit per second is what this is going to do so it's going to move upwards at a rate of one per second so if we go over here and I go say to the side view like this right here's our cube if I hit play we're gonna start moving upwards cool and it's moving upwards while that thread in the background is running so that's kind of cool but let's say that again whatever we're trying to do here right this translate or inside a slow job let's say slow job over here it's job is actually to move the cube okay so instead of moving it in the update we're going to try to move things over here so we're going to move vector3 dot up we can't get time to tell time in here because they're running on different clocks that that's the least of our problems so let's simulate because each tick of slow job is 2 milliseconds so that means our delta time is well 0.02 there you go so this means that if each tick of this is 2 milliseconds then this will move as operate upwards at a rate of one per second awesome perfect okay so this slow job it's entire point in life is to move a cube upwards at a rate of one per second one unit to unit per second I hit play and we're going to get an error look at this and it's not a clock it's money it's not a static Arab sorry it's not a syntax error right if I clear there's no error here this is not a syntax error this is a runtime error boom yet transform can only be called from the main thread what's the problem well you shouldn't teach people this you're opening Pandora's box yeah maybe what is the problem well the problem is you're not allowed oh we got our first tip of the day and um I'm you know what I'm gonna I'm going to save the tips for the end of the stream now save the tips for the end of the stream because we've got it we've got to focus on this and get this done I'll get you distracted I apologize that but it's we're on a very strict time schedule here so we're gonna have to hunker down the reason is you cannot access any thing from the Unity engine asterisk here in anything other than the main thread I cannot access our transform from the main thread I cannot do something I can't even say something like I mean I can't get this far even but I couldn't set something like this position equals a new vector3 zero zero zero like I can't do this it's not that I can't talk all translate is I can't touch this I can't even do this I can't even say this thought game object got name equals new name even this check here run this should throw an error and it does game object only called some main thread I don't think I think if I got rid of this I think I would get a different error but still an error I don't say one ok same one why would they do this why would they make it so that you can't access anything from the unity object space from a secondary thread the reason is if two threads start modifying data at the same time horrible and unpredictable things start to happen imagine this okay this is a bit of a contrived example but imagine this imagine here we had some sort of variable that holds a string okay string poop okay and by default what same start okay I set foo to be equal to recite over here to what I had a good example that was going to be funny and I can't remember what it is dammit I had a funny example I don't know I don't know let's say this string just has the alphabet okay six letters the alphabet ABCDEF okay and over here our job was to simply output you know debug dot log group you know it's a let's say we're doing some sort of student management system right so this is this is a course so we've got that our student is is signed up for computer science and then we have something you know some function here void print student ID right so this this sends phoo to the printer so normally this would print out on a card computer science and this should print out computer science but let's say in a separate thread and again this is a totally contrived example that depending on atomic operations and the way that strings are kept in memory and different things like this may not apply but it should illustrate the problem now we're in our thread in a separate thread we decide to change the contents of poop so at this point we set foo to be equal to English literature which is probably not spelled correctly because that would be you know brilliantly ironic ok got I got it I got it spelled correctly ok in a separate thread we've got that this should print out computer science or maybe it'll print out English literature and that's fine I mean you know if you print something before someone makes a change it'll print one thing then they change something you print it out you'd at that point you get something different that's ok the problem is this and look either either is legit what is it legit is if in the middle of printing the other thread makes a change and suddenly we get a student ID that says um compute literature because we printed out Compu and then the other thread runs literature we've got a really slow printer that halfway through the printout the other thread ran and change the content of our string that's not actually how strings work in c-sharp but let's pretend you can imagine that kind of situation horrible very bad this is nonsense data and in practice it's more like this what if your your thread is doing pathfinding and it's generating path finding route to a destination but halfway through calculating the path finding route right we're playing city skylines we've we've destroyed the road or add a new road or an intersection or made a row to one way or something like that all of a sudden it's like halfway through it's developing a nonsense route that references roads don't exist anymore and so on and so forth we don't know same thing with Dwarf Fortress like oh the Train has changed or it was trying to go and pick up a barrel of beer but in another thread some other dwarf is trying to pick up the same barrel of beer and then who gets you know they're running at the same time how do you deal with that there are a few ways to do that there's um there are ways to lock data and that's something we can do we can say something like over here let's make sure no one is changing our data mid way through our printing operation so what we do is we lock the variable we lock food actually we don't have to lock we can lock anything so we can have a generic object front door no equals new object literally be anything we're going to lock the front door while the front door is locked we're going to do something we're going to print out the student ID here safe in the knowledge that nothing is messing with our data and then over here in our slow job before we change foo we're going to say ah let's lock the front door and then we're going to change foo these locks are mutually exclusive okay if the printer over here has locked the front door if front door is already locked the thread will pause until front door is not locked you don't have to explicitly unlock things with these because of the way the blocks are let me just check that I get the syntax right no syntax errors there you go good there you go so now if the printer is in the middle of printing things because we've locked the front door the printer is printing things in our second thread here if the front door is locked it just hopes it'll wait for the lock to be released and then it will continue running and change this so we won't get the half in half anymore so why doesn't unity do this with all of its data right because that's the problem if we allow threads to start altering things let's say our thread is supposed to change our position right this about transformed opposition equals new vector3 you know and we're setting some coordinate systems here well it's possible that halfway through rendering our objects these numbers change or worse you know so we're changing it to like 5 10 20 over here what if we're half where we're halfway through rendering our object or or calc updating our physics system and we were going with the coordinates 0 0 0 and now we're all of a sudden halfway through this now we're sending 0 0 20 which is way wrong right five zero zero zero is fine 5 10 20 is fine zero zero 20 is right out and all of a sudden we've got that kind of situation that happens again some of these changes your atomic and whatever but horrible things start to happen so clearly it would be very bad if um Rad's could change Unity's internal data willy-nilly in a very disorganized way actually like okay well what if you know so can't unity just lock some stuff yeah but lock what and this is voluntary right you know you can you can lock something like let's say we lock the transform or you lock this or again it doesn't matter what we lock the lock is arbitrary you're not walking the object this is one of the hardest things to sort of wrap your brain around you're not locking anything specific you're just locking and object as a reference to the lock you're locking front door in this case front door in our example front door is kind of securing food we could also lock food in both these places makes no difference as long as you're locking the same object the logic is exactly the same so what do you lock how do you ensure that people are consistent about it maybe you're locking this or you're locking this top transform or you're locking this transformed opposition right and 99.9% of the code you're ever going to write is going to be single threaded all of these locks operations have a little bit of overhead about them so now the Sun we're adding a crap-ton of overhead to the unity engine for something that's going to come up only 0.01% of the time so what's the point so instead what unity does is a very simple thing what unity does is before it's you like when you call any of the unity functions it just checks thread dot current thread ID the manage thread ID somebody that it basically just makes sure that it's thread 1 that it's the main thread basically is what's going on so unity is always before it like it lets you make any changes to its data it makes sure the changes are coming from the main thread and it's like that's a very easy very fast operation and it makes sure that nothing can possibly s up but it makes it hard do certain thread of things very good can you actually just make a normal mutex yeah absolutely you can do like there's you can totally do that cuz lock is a short form for monitors in in c-sharp so there's this monitor thing which is lock the lock statement is creating these monitors which is one way of doing it and yes you can also use mutexes to do things like that absolutely right but that doesn't resolve the problem of of the situation and how unity should treat it so unity is taking the stance of while it does use some multi-threading internally for some stuff it requires you make all changes to unity objects in the main thread now there are a couple of slight exceptions in a sense in that it is perfectly fine for us to make a vector here some vector equals new vector 3 0 0 0 this is OK we run this we're not going to get any complaints we'll see no complaints to run our program is running fine this even works this even works if this is an object so this is a vector that can be say modified in our secondary thread it can also be modified in our primary thread so here we're going to set it to 1 1 1 and in our update or does our update function over here in our update function we're going to set it to 2 2 2 this is totally fine so now all the sudden we have this vector 3 object that we can modify in the main thread as well as their child threads and that's totally okay because unity has implemented vector 3 as a as a basic value this is this is actually one of the reasons you can't you can't have you have a no vector I guess you can but it's this is fine it's a simple data object that doesn't do much um and oh right this is the reason well this is one of the reasons in unity even in the main thread over here right or even in the main thread in unity you can't say this dot transform dot position dot X is equal to 2 or 4 whatever this is actually going to lead to a syntax error right here cannot volume and modify a value type return value of blah blah blah blah blah you can't do this in unity what you have to do is say something like the vector3 pause pause is equal to this and then you set pause dot pause dot x2 that and then you say transform equals pause okay now you can't you still can't do this line here in a secondary thread but you can modify a vector as much as you want in a secondary side vector two vectors threes are fine textures are not because textures actually do a lot of their operations like via the video card and different things like this so you can't modify textures in a secondary thread this actually is a very important thing because one of the slowest things you can do right is let's say we had a texture so I have a texture 2d some texture okay and let's say I want to loop through all of its pixels which you can totally do right we can have a for loop that's for X and some texture dot width for y some texture dot height and then we can say some texture dot set pixel XY and you set it to some sort of color color dot black okay something like this so I guess I should probably just person tax reasons go ahead and make this and that and that and that okay this is an example this is a super slow thing to do the big loop if you're talking about you know your standard 1080p textures nine twenty by ten eighty multiply them together is it like it's like it's like two million right so two million loops and this set operation as well as get pixel mega slow you can't actually do this a lot fat n and here's the problem because this is an operation on a texture you cannot do this in a separate thread if you try to run yet pixel or set pixel in a child thread unit you would say no no it's no good you're not in the main thread but there is something you can do while that is a slow and B can't be threaded what you can ask for instead is you can ask for some texture dot yet pixels this returns an array of all the pixels okay and first of all this is a lot faster and secondly secondly unity is perfectly happy in your threads again this is a slow job that's in a thread it's perfectly fine saying hey you can make changes these pixels sure that's okay Unity's fine with doing that but when you want to take these pixels and actually apply them back to the texture you can't do that in the sub thread so this would be a good example of run some thread and sew some thread is running it's running elf the thread is done so copy the pixels back to the texture write some texture dot set pixels and then you pass it the colors or I call X are called pixels like that obviously you know you wouldn't do this every frame you would you do some sort of check to make sure that you know this is a frame that is finished and so on and so forth but that's a way of offloading the job to a thread you copy all the data you pull all the data out of the unity objects this exactly the physic system works in unity you take all the unity data the data from the unity objects you need copy them out to you know just floats and color and vectors and all kinds of arrays like that right or you can create your own datatypes that's fine as long as they're not unity objects copy all the meaningful data out into some temporary spot run your thread using that data and then when the thread is done take that data in the main thread take that data and copy it back into the unity objects done now you have a job that runs on a separate thread in the background and it's safe for unity it's actually a great way of operating things what do we got we get ten minutes okay ten minutes we can this is this code have gotten very messy but there are different ways to set this up a little bit cleaner which is kind of nice right so let's do an example over here can I do a good example in ten minutes of something that's kind of cute like and a little bit more flexible sure let's do that see what I'm going to get rid of example script here and do some no don't don't delete the script we're going to keep that script in there just not doing anything with it so let's do something like thread cuher okay and this could be a static for the sake of argument let's just make an empty game object and throw that on there and we're going to rename the game object to thread cuher we'll pop this open oops pop this open excellent so you do need unity or system dot threading to just you know reference that stuff but what we can end up with is a cool system like this um let's make an array of like a semi using system we're going to make an array make an array to hold functions let me get back you make a public function here and again you probably want to do the static system like this but let's do something like start threaded function okay we want to just some little helper function that it's job is it's going to take in all function now I'm going to call this action some function okay action is a short form for a delegate that takes no parameters and returns nothing what's the syntax see sharp delegates we'd want them just want to make sure you get the syntax right of course sir so if we had public void same thing as action like that so this is a sorry public delegate what the word delegate drives me crazy delegate equals blueprint for a function signature dela well spelling delegates so here we are just saying we need a function that returns nothing and requires no parameters okay so what I could do is do this so this requires a same thing as action or we could just say action the nice thing about action is it actually is a generic so you could always do something like this so now the sudden we'd be driving some sort of function that requires an integer supply that what we can just do this as a short form and I like it because it looks clean so all we're going to do here is we've got the start threaded function so we can say something like okay so that's easy we can just say thread t equals new thread which is going to run some function and then we're going to start it okay so we've got that well I want to do this example where in my start we're going to have that then I'm going to have a slow function that does a unity thing okay so this is going to do first we do a really slow thing so we're just going to say sleep for two seconds play for two seconds this is our really slow thing after that now we need to modify a Unity game object something like this not transform dot position is equal to new vector 3 1 1 1 not allowed from a child thread so this is our problem we need to do a really slow thing then we need to update at Unity game object and again we could set things up to copy things to you know someplace older you know some placeholder vector so we can copy it in there and then we have to make sure to check that this threat is finished and then take this vector and apply that to division I don't like bookkeeping like that I don't have to remember to do a thing and to sync everything up so let's let's create a new function here queue main thread function action some function we need to make sure that some function is running from the main thread so here's here's the example here's my solution here right what I can do is I can say a function right is equal and you can make you can make these sort of functions on the fly so this is actually describing a function which is going to set the transform position to 1 1 1 so the a function this is the function ok so I can call this ok note we still aren't allowed to call this from a child thread so I'm not I'm still not allowed to call this here because it's trying to do something very bad very bad because we're modifying unity object so I can't do that here but what I can do instead is say something like Q main thread function I need you to run this function on the main thread now again here if I try to call some function this isn't okay if we're in a child thread and at this point we still are slowed function that does a unity thing is running a child thread which means everything that it does is happening in a child thread including calling this function which means in this function we're still in the child at this point so we still can't call some function so what's the solution well what if we did this what if we had on the right I guess a list would be better let's say we had a list of action called something like functions to run in main thread like that and we made sure that we instantiated this so this is a new list that holds actions so we're going to say ah alright our list of functions to run in main thread we're going to add some function to it so now we have a list of functions that need to be run in the main thread what runs in the main thread update always run in the main thread so we can say something like while functions to run in main thread while functions throwing the main thread dot length or count for this count is greater than zero what we're going to do is we're going to take the first one so action some pump equals function toreno means right we can't do a push or pop or anything right so I've got to go and grab the first one the last one would be more optimal screw it oh let's assume we want to run the minister to the quarter that's true grab the first / oldest function in the list then we're going to make sure to remove it remove at zero okay so we've removed it from the list now run get some funk like that so now we've got a situation where we are only running this function in the main thread so now we've got this really nice little setup where I can do something let's see let's add a couple of comments and debug dot log are the results of the child thread are being applied to a Unity game object safely okay so let's let's do this so slow functions does an interesting unity thing first of all I can call it directly right so I can do slow function that does unity thing I can call it directly the downside will be it'll hang our output for two seconds and we can confirm this right we can do debug log start started Starkid and then done okay and so start started started on so what's going to happen now is nothing is going to happen for about two seconds and then we're going to see all at once start started then we're going to see start done after the two so yeah so back to back because we're not going to see this to the log until the whole brunt thing runs and then we will immediately see the result of child thread is being applied to a Unity game object cuz what's happening is we're going to do this we're going to run slow function as a unity thing which will hang for two seconds then add a function to the queue and then do this and then on update it'll run it from the let's give that a try and see what's what which actually instead of a list it should use a Q object yes I should we've got a syntax error with an invalid argument here oh really really it doesn't auto translate from one to the other okay so when we were using a function name here you know something like this unity is okay with that because it automatically converts from a generic function name to a thread start object but it doesn't know how to convert a action to a thread start object so we just have to put in a little bit of fluff and say new thread start and it just needs to be passed some kind of delegate which action is or should be there we go we're good we're getting warnings because some variables aren't being used but that's okay all right so again if I run this nothing should happen for two seconds and then we'll get all the stuff BAM after two seconds start started start done the results of the child thread are being applied to the Unity game object we can confirm that our thread cure is now position 1 1 1 okay but we don't want that that two-second delay we want this to be threaded so instead of running this directly instead what we're going to do is we're going to say start threaded function which doesn't do much we could do this manually but it's going to be ok so we're going to say start a threaded function slow function is a unity thing so now what's going to happen is this we're going to see start started in the log this is going to start a thread will instantly start see starts done in our log and then two seconds this thread is going to finish add the thing to the queue and then this will be DQ'd over here and play we see start started and done right away then two seconds later the result of the child thread is being applied to unity game object safely our thread cures of one one one look at that we are now in a situation where we can run a fat fat job in a separate thread without hanging the main program but still have the ability to have the result of that thread be applied to a Unity game object without having to jump through a bunch of loops with like passing the data back and checking to see if it's changed and do all this instead when the thread has done its job it's just whatever it's supposed to do at the end of its thread over here it's just us it in action and then tells our little helper hey run run this action as soon as you're on main thread so next frame it'll run this action on the main thread force which just allows us to do this so that way slow function that does a unity thing does everything in one place whatever it's supposed to do in unity whether that's this whether that's supposed to be creating a new train object by the way that's going to be super relevant to project mighty spud just a hint if you're following project mighty spud the super mega relevant because we can't make terrain objects we can't make changes to the terrain data in a separate thread because it's a Unity game object but we could do a bunch of calculations on the thread and then after that's done in some fashion make the changes of the game object and this is a really handy dandy little way of doing it you just sort of queue it up and forget about it no worries so then so what this is the thread cure normally this thread cure class here wouldn't have this it wouldn't have the implementation slope function does the unity thing the your your function the slow function that needs to be threaded can be anywhere in your code anywhere at all and then it will just call thread cure and tell it to do something and in practice you probably want to use statics and things like that that way you don't have to find the instance of whatever but you know that'll be a thing for us to follow up on in in project mighty spud but there we have it so we did briefly touch on locking I really want to avoid anything so that you need locking for which means in practice you might still have to pass some data to this right you might have to take a bunch of gay Unity data and pre bundle it into into something so that your SLO function that does unity thing be reading some data that safely because it can't read from a game objects not that it just can't change a game object it actually can't read from a Unity game object if it's not in the main thread so you still have to do a little bit of repackaging but that's actually something else that you can sort of work around because there's no reason that you couldn't say hey I need I need some data here I need you know I need a vector3 foo I need an array of floats called bar and I need an array of colors which are some pixels I need that here now this start threaded function currently requires an action that doesn't have any parameters and threaded start you use parama ties start you can do a few different things like that it's a little Trixie right right now this wouldn't work the SLO function does unity thing needs three parameters and our start threaded function is explicitly asking for a function some function with no params okay like that so clearly that's not what we're passing the start threaded function but that doesn't mean we can't again wrap all this what we're going to pass the start threaded function is actually this anonymous function kind of a lambda over here which inside it is going to run slow thing that that does a unity thing which we then pass you know vector 3.0 that we pass an array of floats you know new float four and a array of color objects you know 100 colors or something like that okay so now what happens is we've got this anonymous function but doesn't run right away we're passing the anonymous function to start threaded function and then when it finally runs it runs the anonymous function which then calls slow function that does a unity thing while passing parameters it's kind of an easy way to sort of cheat this situation without having to do you know like object params or or you know there's a lot of different ways you can do it I think I'm just in love with anonymous function lambdas and things like that so I tend to do things that way but your mileage may vary so I think I'm gonna leave that bundle as isn't you can verify one more time that didn't random syntax errors in the end no if I hit play again we'll see start and done right away two seconds later the thread finishes queues everything up and it runs on the next update like that and like magic it can work too so it doesn't solve all your problems but hopefully we address some of the issues the number one rule about multi-threading in games is don't yeah the second rule of multi-threading games is don't the third rule is if you're going to multi thread make your threads do exactly one thing only and finish they should be focused on a single task bundle up all the data they need to do that single task and then at the end sort of unbundle the results in some fashion keep it very very tight and organized your your threaded code should never have to ask one of your other objects anything you can do things with locks there's a few different ways that you can sort of work around this but you're going to create problems for yourself it's going to be buggy it's very hard to debug this code because things happen in all kinds of different order so it can be really hard like your debugger is going to be harder to work with your debug logs are going to be harder to work with it's a big big big big issue the nice thing about it the best thing is there's no reason we can still call slow function that does unity thing in the main thread right we did that as an example so I could just instead of instead of instead of sending asking it to be a threaded function I can just run it directly when I'm debugging things when I'm testing I can run the function directly yet I'll stall for two seconds but I'll be able to see the results properly and everything will run in a certain fixed order so I can get a good sense of the debugging and then after I can say okay now now we run it in a separate thread although most a lot of the time you're going to have something that works fine not multi thread and then doesn't work here properly that's usually what happens if slow functions does a unity thing is that your SLO function has to read anything except this then you might have issues right because if you can't read unity object but you could read your own objects in classes from over here so I can have you know some glow old object dot some parameter you know I can read that totally that that is legal unity will let you do that if you know this is a custom class what do you want to so there it is the oft requested unity multi-threading tutorial honestly I find this simpler than working with co-routines and also you are going to load balance your CPUs a lot better this way than using co-routines the difference is with co-routines you can access unity objects without any difficulty whatsoever so that's quite cool the problems co-routines is either you're going to use for frame every you know loop or something eyes out or try to guess or you're gonna have to use a stopwatch inside of your your co-routine loop and then say oh 10 milliseconds is gone that's too long we've got to go and yield for a frame or something which gets to be work here it's just running on a separate cpu basically or even if if you had some sort of weird ancient computer that just had a single CPU with a single core which is pretty unlikely these days you would still the OS would still be responsible for doing time slicing and it still means you wouldn't have to it to worry about it manually big issue with multi-threading does not work in WebGL no threading in WebGL I'm going to put a note here big huge note WebGL doesn't do multi-threading at all so if you're exporting your game for the web this won't work I I don't know if it just sort of like pads around it and just runs in any way or if it just throws up an air and says like you literally any time you try to like you know create a thread object it'll just throw an error like at compilation when trying to compile your web thing it'll say you can't do this buddy this this does not exist where you're trying to go um so yeah you know that's good and by that I mean bad because all of a sudden you design a program that can't necessarily run on every platform that unity supports but you know if you're going to I'm making a PC program or console programmer whatever I mean it's really not an issue other than WebGL and at some point WebGL may have multi-threading but it definitely doesn't now so big huge note my bad fake huge note no unity objects no multi-threading milk in WebGL co-routines pooling oh um we won't have time I've already gone over by 12 minutes actually thread pooling is another thing you can do when you're saying start threaded function over here let's say you've got a situation where you're trying to start like a hundred threads let's say you're doing Dwarf Fortress you've got 100 dwarves you just started the program and every single one of them needs to pass find some destination you may not want to start 100 threads first of all there's all kinds of different like system limitations and and and whatever might not allow you to start 100 threads simultaneously instead you can do a pool there's no reason we need to start the thread right away because what we could do is functions waiting to run on child thread and then Mac's running threads like this you know equals 5 and so if you already have 5 threads that are running instead you just add it to this list of course that would mean you'd have to keep track you'd have to have your list of thread running threads like this and it's like are you sure you can do that that's a pooling setup there's many things online to to deal with that mmm and executing some things our main thread okay so we didn't do a full look at thread cooling but uh good enough so I'm gonna wrap this up I didn't throw over to a kiss for luck stream she is streaming right now she is doing I knew this a second ago she is she's destroying oh I think she's doing me that that new um rescue 911 emergency responder game which I have played I haven't recorded a video for yet it's quite fun so I'm going to be hosting her thing so see that tasks tasks are basically what we've implemented here a task the problem with this there's a fan-freaking-tastic class called tasks in c-sharp say oh it's ready for us oh my god well result we just have to include something no we can't why don't we have access to I think it's system DUP threading that task amazing class really convenient the version of the dotnet library that unity uses does not include the tasks class so you can't use a task class which is basically doing something somewhat similar to what we're doing here and also some of the tooling classes there's some extra multi-threaded classes that exist in more recent and complete versions of.net that are super convenient to use to generate to manage many many many many many threads as well as doing things like executing various things in the main thread or a child thread or whatever they're not available the current version of.net internally they're just doing exactly what we're doing here they're not they're not magic they're not something you can't do they're just convenient you can do some wizardry to update the version of the c-sharp compiler and the dotnet library that you are using in unity to have the newer model of dotnet but it's a pain in the ass and ultimately not 100% required so I'm going to bundle this up I'm going to get this up over on quill Hakeem comm flash unit each twirls and that'll be it next livestream is going to be on Saturday at noon and we're going to be playing something I had a plan and I don't remember what it is we might be playing that new torment game we'll have to make a decision we'll see how it goes my cam is blocking the code oh it's probably been like that yet for ages so my bad terribly sorry I should have been over there and that's what happens when I'm trying to rush because I'm trying to squeeze in too many things and now I still went over now by 15 minutes thank you very much for watching folks I will see you guys on Saturday enjoy watching a kiss for luck and I'll see you soon
Info
Channel: quill18creates
Views: 58,229
Rating: undefined out of 5
Keywords: Unity (game Engine), Game Engine (Software Genre), programming, tutorial, first, person, shooter, first person shooter, fps, howto, how to, beginner, guide, unity, unity 3d, unity 4, physics, blender, 2d, 3d, quill18, quill, multiplayer
Id: ja63QO1Imck
Channel Id: undefined
Length: 76min 6sec (4566 seconds)
Published: Mon Mar 06 2017
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.