Designing and Evaluating Reusable Components - 2004

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
code reuse is something that in general everyone wants to be able to do either in-house or externally you typically want to be able to solve a problem once or license of you know a solution from somebody and just integrate it into your game and have it work it's kind of a goal that's common across many industries not just the game industry and so there's no surprise that there's lots of books on it there's lots of people who speak on the topic this sort of thing but in general what seems to typically be the case is that a lot of the things that you hear about this or think you know about this when you actually try to put them into practice it turns out that it doesn't really work that well and you kind of sit there scratching your head going like you know we thought that we were doing some of this code reuse stuff well or we we thought this component that we looked at to license had a good API but when when we actually try to use it in our game we kind of like start to feel like getting a little more NIH about the problem because God just didn't work out that well caused all sorts of problems and so on so where this lecture kind of comes from is I worked at rad game tools and I was responsible for doing granny which is the character animation package and at the outset of that I kind of made the first version of it according to what I thought were a standard you know reusable code print principles and it turned out to just not really work very well you know a lot of people had a lot of trouble integrating it it was just you know things just didn't go the way that I thought they should so we spent a lot of time when we went from 1.0 2.0 kind of looking at that and going you know what what happened there and we learned a lot of things and the second incarnation of the product actually is used in I think 400 different SKUs which is a vast vast increase from the original one so with this lecture I kind of tried to go back and go okay what were all the things we learned about that and how did you know how do we make that jump are they generally applicable and are they are there like things that we can kind of generalize on this that are real rules we can use to get reuse in the game industry that are from practice and not just some kind of theoretical thing that doesn't seem to really play out in practice so before I get into it I just wanted to quickly go over the types of reuse that we typically see the first kind here on the left is what I call lair which is sort of like an OpenGL or direct3d kind of a thing where you've got new code that you're writing on top which is your game or something you've got reused code which is the layer which is the the you know direct3d part or whatever the API is that's provided to abstract some service that's down at the bottom which is like the 3d hardware or something like this and this kind of reuse this is pretty common now and it seems to be working relatively well there's still problems within that sort of thing the second kind is engine code which is kind of inverted it's like the reused code is up at the top making all the decisions it's the majority of the system and you're just writing kind of this smaller subset of that conforming to its rules giving it information back to define your game and it's pretty much responsible for ending up dealing with all the output and that sort of thing and then finally we have the third kind which is kind of the new thing which hadn't been around really in any successful way up until very recently which is components and those are things where it's still kind of like the layer situation where it's the new code is up at the top you know the game code is is all in control but there's kind of these little pieces that you can license which not only accept input from you but actually give stuff back you that actually determines how the game runs they're tightly integrated and these are things like character animation physics AI that sort of thing and they don't really talk to services directly there's no well-defined output layer for them they just give you stuff back and then you're responsible for kind of doing it now the reason those are important and the reason that layers aren't the only thing that we need I mean engines I'm not really going to cover because that's just you know a totally separate topic the reason that the components are important is because if you look at layers they're very limited they require there to be some well-defined kind of service at the bottom that they're abstract Ihara and if they don't have something like that so if you want to something like AI where there's no agreed-upon standard for what the output is then they just you know they can't really exist and similarly if you have you know two layers that you're trying to use in the game simultaneously well that just isn't going to work at all you know if I have two things that expect to be talking to the 3d hardware that can be a huge disaster because each one wants control over it and how you broker between them so it's no longer this clean like I give something to it it puts something out now components solve both of these problems they don't need a service at the bottom they can just be there they get you know you give them data they process it they give a data back it's all good and similarly if there's one service you know one output that you're that you're dealing with that both services are consigned that both components are concerned with that's fine because the input is coming back into the game where you can broker between it resolve conflicts do anything you need so components are really like the most powerful form of subsystem reuse that there is and it they're the ones that we happen to need to solve a lot of the problems of reuse that we you know for components we don't have currently the problem with them is they're much harder to design and the reason for that is this little back channel here that I drew I mean it's like a little simple arrow but in reality like the fact that it is coupled back to the game and you have to rely on what it's giving you to make gameplay decisions to do all these things really does mean that it's a vastly harder design problem than just output only and not to say that you know directory in OpenGL weren't hard to design it's just these are even harder and that's unfortunate so what I'm going to do here is for a couple slides I'm going to introduce kind of like a model for how to look at the way games integrate things which may or may not be unique to how other industries integrate things I don't have much experience with that but for games there's a very specific way that integration typically happens with components so I'm going to go through a little bit of that and then I'm going to go into some really very in-depth code snippet kind of stuff where I take you through all of the different ways that kind of you can use this model to look at what's happening in making decisions about either is some licensable API that I'm going to use going to work well or I'm trying to design an API that I can reuse in-house or for other developers you know what can I do to make those API is better so here's my little sort of pseudo graph the vertical axis here is how much work was done to integrate the particular API into our game and down here is time and what some people like tend to / if you read books about this sort of thing is that integration isn't really something that you just sit down and do we don't like one day decide to integrate physics into our game we spend a week on it and then we're done and that's the last we hear of it that's not what happens what really happens is yes that that part of the process is over here somewhere we do the initial integration that's in the game but then in reality we get usually as we go we use a little bit more of it and then we get to some point where we have some major push that we're trying to do and at that point we typically have to start getting more involved with that API because there's things we need to do like for example we need have memory budgets we need to hit we need to add some features that we didn't think we were going to have to need or that we put off integrating and now we need them for this demo we need them for whatever we're trying to ship and we get kind of this little spike there and similarly that typically happens again at least once at the end of the project when we're trying to ship there's all of these hard constraints that we have to meet so it really is an evolving usage it goes from from initial where we may even just be prototyping a game so we're not even doing the official integration all the way till like we're trying to ship and nail down every little last piece okay so here's the kind of like abstract part so try to put on your abstract cap what I want to do is get you in the in the mindset of thinking about the options that you or other developers have for integrating a particular thing into their game a particular component now I took time out of this now so we're just looking at it a single point in time and the amount of work to integrate something is here and the amount of benefit to the game is here and these little big blue circles are just representing options that the developer has for integration so you know when I go to integrate something like character animation to my game there's a number of ways I could do it I look at the game engine I look at the component and I go well we could do something like this we could wrap it like this we could put it in over here we're gonna have we can fork it and put it part of it over here and over here so you've got a number of ways in which you can integrate it and typically as you go you know the more integration work it is for you typically the more benefit you could get because the tight the more tighter coupled you become to this component the more things you can do with it you know if you start managing its memory for it then you're gonna get performance benefits and there's all these kind of things that as we go up so what happens is when you initially integrate it in you've got some minimum bar that you're trying to meet in terms of benefit you've got like we need these features for the game right now so we're just going to try and meet that and you know hey you have particularly smart programmers so they're gonna kind of go well this is kind of the thing that's the least amount of work that gives me the most amount of benefit that meets the bar right but you know I could do a little more work and maybe get something that I thought was a little more beneficial so maybe I will you know so there's kind of you're gonna pick something kind of down here to get it in get it working get the game running and get on with things then as you go at some point there's going to be some new requirements you're going to need more benefits from this API and when I say benefits I don't necessarily mean features I just mean there are things that you need from it the ability to like get its memory footprint down the ability to like reclaim some processor time those things are all considered part of this because you know again they're integral to shipping the game so as this bar moves across well you're kind of going to start going okay well we need to kind of integrate a little more kneading great a little more lead to change what we're doing call some different API do that sort of thing and you move you move through this kind of space of possibilities that you had and then finally what happens if if you you know you get into trouble is you get to some part where the requirements change and there is no easy jump to get you there right you have this thing that you have to do and maybe it's like some kind of hard hard budget that you have to meet and you realize that the way that you were using the API there just is no simple fix there's no way that you can get say streaming what you want to implement in your game into this component in any reasonable amount of work it's going to be this massive thing where you have to do all sorts of stuff behind its back and do all these kind of things so you end up looking at a situation like this where you're like wow to get this extra thing that we needed to do to ship this game we have to do a massive amount of work to deal with this component so to kind of summarize that it's like hey if the API has these little steps and I want to meet this minimum bar of features that I've got to get for this next drop I'd want to go from unsolved to solve I just want to take a little step while I do some work to get it whatever I feel is the minimum work that I should generally have to do to get this thing in there but if there is no options for that then they typically will have to jump all the way to something which while you had to do a ton of work to get there it really you know solved a lot more problems than you were looking to solve you ended up doing a lot more management or a lot more work with this component than you would have liked and maybe there are fringe benefits to that but a lot of times those benefits aren't really realized to you you just wanted to manage one little piece of it for the component for example but now you ended up having to manage all of its memory let's say so I call that integration discontinuity which is like I'm kind of going along using this component and all sudden I hit this wall and I'm like man this is a disaster and unfortunately they typically happen around ship time just when it's like most unfortunate to have such a thing happen so I guess this is this is kind of a little like beating a dead horse but just to go through it if you think about this yellow line as how much work you actually did to integrate the product meaning I'm actually doing this work and I keep doing work I do more work I do more work when you have these discontinuities in there what happens is you're really doing more work than the benefit that you are getting right so when I have to do more work than the minimum that I wanted you to get this feature in then the actual benefit to your app isn't going up commensurate with the work you're like spending a lot of extra work to work around this API that you're forced to deal with and this doesn't even have to be increasing because what could happen is if you're spending time integrating opponent you may find out that the way that you were integrating it isn't actually working and you have to like rip some of it out redo the way you were doing it throw out code you wrote to reintegrate it a different way so you can actually end up going like okay we got to tear some out we tried this other method and that didn't work so then we tried going around to this other way and we finally found an end-run that worked and now we keep going and so on so I guess all I'm trying to say with this graph is like it's non-trivial when you have these like API problems in there it really can force you to blow out a lot of work and that's why I posit that's like the real problem the goal that we're trying to solve when we dissolve reusable api's is to think about this and to try to make it so that at all times as people integrate the product they are always able to do only what they think they should have to do to get the next thing that they need out of the API and not waste all of this time because really when they use code I mean from a license of perspective that's what the customer ends up remembering right they don't remember if you save them some work at some point they remember when you caused them this huge disaster right around ship time right and similarly if you're the person doing that you don't want that disaster so this is really the thing that I think is most important now unfortunately the current trends in API design a lot you know with things were you're kind of wrapping stuff up doing a lot of encapsulation doing a lot of insulation what they actually do is they take the number of options that are available to the developer which are these you know blue things like I said before and they start to reduce them they go you know what you can't really have access to these things you can't call these things that calling these other things and at some point sometimes you even get to the point where you look at an API you're like you know what there's only one thing I can do with this like they've totally walled me off I have no options anymore this is exhausting these kind of components typically fail but a lot of times we're stuck with just this kind of thing where you look and there's a low level or high level of would use and so on and I argue that really like you know we want things to look like this and ideally fill in this gap that I was talking about before ok so that's kind of the end of the abstract part so now you get now you're gonna have to read some code snippets on here so hopefully can everyone like read this well enough to see say that same thing ok good so these are the characteristics that I have identified that I think are are sort of indicative of how that those those blue points because that was very abstract you know I drew a graph had some blue points on it you know well that's all great but how do I actually know when I'm looking at an API you know is it going to have these problems are there discontinuities in there you know how much options does it really give me and originally I only had four in fact if you look in the in the in my lecture notes there were only four but I was showing some of the slides to Chris Hecker and he was like you don't talk about flow control enough you got to talk about flow control it's it's implicit and all the things are talking about not talking about so I looked at it and he was toad right so I added a fifth one which kind of like does really it does really matter so props to Chris for that the five things are in order granularity which is to say that you know I have some API and I can split it into smaller API so if I have an API a I can replace it with two api's that do the things that it did but give me a little more control there's redundancy which is to say that there's an API that does something which is a but I could alternatively call some other API which does the same thing be only maybe it takes slightly different parameters or it does it in a slightly different way so I have some you know some options there there's coupling which is when you have one thing and if you do that thing in the API you're required to do some other things so there's like this hidden kind of link in between them that you are not really able to overcome there's retention which is when the API kind of has two things and like ones on your side and then the other is on their side you have to kind of mirror them like making a scene graph something like this and finally there's flow control which is like who's calling who am i calling them is the component calling me am i calling it and it's calling me back you know what's going on here now the important thing to remember is these are just characteristics and each one of them has trade-offs which I'll talk about later so it's not some of them are always bad but the majority of them have like sometimes it's good to have have like less granularity so that's good to have more so you know don't think of them as like hard-coded we want all these five things it's like no they're each one of them is just a characteristic and we'll talk about how to interpret them a second so to go through the kinds of granularity some of them are non-obvious the most obvious thing is hey if I have something like update orientation and what this function is supposed to do is the API has some measure of my orientation that it's keeping and it's got some measure of the change in orientation and I want to go ahead and apply that change so now when I use the orientation it's the new orientation well you know simple granularity changes okay I want to break that down in steps I want to get the orientation myself I want to get the change in orientation myself from the API and then I want to you know set the thing with that change and these are angles or something so that's not like any kind of overloaded plus operator just like you know hey it's like you know just a regular 2d angle that's the most humble sign of granularity the reason that I want that is because hey I may want to modify whatever is going to happen in there right I may not want it to just use it directly the change that it has I may want to play with it right and similarly the kind of less obvious version of that is I may not ever want to change it I may want this to happen exactly the same way that it would have if I just called it but I have this other thing that I want to have happen so instead of like modifying things by inserting myself in the middle really all I'm doing is I'm separating when the API is going to do those two things and that may not be that important in other industries but in the game initially that's like crucial because sometimes you thread things sometimes you have like kind of things that you need to hold over till the end of the frame so you really don't want to be in positions where hmm you don't have that kind of control so that kind of granular is also important so okay so now let's talk about redundancy because granularity hopefully that's pretty clear so redundancy is just you know in this most basic form is something like this I wanted to pass a three by three matrix before and now I want to pass a quaternion so the API gives me two calls and I can enter in either way and it just accepts the you know the type of primer that I was looking for doesn't do anything different C and D are a different way of looking at which is that hey sometimes I figured that there should be like these basic things that it can just do for me I don't wanna have to make my own identity matrix and password that sort of stuff there's just constants that the API builds in that's easy for me to use and that way united's knows taken care of and similarly oftentimes there are things that I would do with the orientation and I just want it to do those basic operations for me to set the operation I'm going to set the orientation rather and that's D so those are just some different redundant ways of doing that now the sort of non the the sort of subtle way of having redundancy is this kind here where if you remember in the previous slide I couldn't fit on the slide we had this kind of operation where we're getting the orientation changing your orientating the change in the orientation and then setting it well if I was to go up a level of granularity from that I could have the option of bundling those three calls in two different ways I could bundle the first two calls and leave the third one Lola I mean the the the finer level of granularity or I could bundle the second two calls and leave this one at the finer level so they're both like kind of equivalent they're at the same level of granularity these two snippets but they have different choices in redundancy in terms of they have different choice in what to bundle which makes kind of a redundant API there so that can typically be pretty useful as you make coarser grained versions of an API to have the user have the ability to choose which ones they're going to bundle which ones are not so now we get to coupling which is not really a trade-off thing coupling is pretty much always bad but you know it's usually also unavoidable in a lot of places so the simplest kind of coupling in an API is when you have something that does like a bunch thinks things to lots of objects and you have no control over that so you know a tip very typical thing is like simulating the physics simulator where it's just like hey you know I'm maybe I wanted some control over what was getting simulated because I have some special things that I want here but like maybe this API doesn't let me do that so I have to just like have everything happen at once okay so that obviously that's bad coupling that's it you know inter object coupling the other kind of coupling is hey I've got some api's which kind of depend on this one state that I said so maybe I call set time and it written you know retains this time information but then like lots of different api's use that so I'm current of creating a hidden sort of coupling between those api's in the sense that like you know now they all have to kind of have this right ordering of if I set the time and then call this I can't then call the other thing which counted on the time being thing for the previous room so I'm like kind of having this kind of like a hidden coupling but I have to think about in my head the snippets see is the kind of coupling where you have in like a GL begin GL end for example which is that there isn't any identification for a particular lock that I'm doing so I can only have one of them at any given time you know I don't specify I don't really get anything back from this and I don't pass anything into this so there's really just this one implicit lock that either I am or I'm not using and that kind of couples it in the sense that two pieces of code can't do that you have to make sure that all the code oh he's just as serially doing something like this d is very simple which is hey if there are internal buffers are those internal buffers going to be like things that we have to pay attention to so in this case if I was returning a care star for example this is probably returning the same buffer as the so I've got this kind of hidden coupling of like string one actually becomes the same thing as string 2 here because you know whatever well hopefully that doesn't happen too much in modern api's but you know figured I'd mention it snippet II is kind of a more insidious form of coupling it's when the allocation of something is coupled to its initialization and a lot of api's have this problem unfortunately a lot of times developers don't really want to have to be able to say oh you know what get me the memory for this thing and initialize it they might want to go like I'm going to provide the memory and then could you just initialize it in place or you know what you're managing the memory but I need to initialize this guy because I'm like reading it from the special stream that I have packed or whatever so that's a kind of coupling we're like I bundle two things together that cannot ever be separated F is where I have coupling between some special type in the system like if I only accept a matrix for my orientation this is kind of gets back to the dunce a thing if that's the only type that I accept here then even if I have some like representation of the matrix myself I have to constantly like make it with the other you know with the AP I have to say like hey make one of your special fancy matrix objects so that I can actually call your functions and the final kind of coupling is when you depend on when the API doesn't let you get away from depending on their file format so in this case it's like if the only way to get an object is to read that object with ver file reading routine right I can't construct it myself with my own reading it in then I'm kind of dependent on their file i/o routines and their data format and there's nothing I can do about that really okay retention is pretty simple so not many code snippets here the idea there is just hey if I have stuff that is you know data that I kind of own or that I am the one who's like in charge of but the API forces me to announce that data to it and it keeps a copy that's retention so in a it's the simplest kind I have you know I'm going to set the time or I'm going to set what I think the value of pi' should be for the application then they just retains that if Eric's going to use it everywhere in B we have kind of like yeah well you know what I'm going to tell you that this object is parented to this other object so every time you do something like up to the orientation you're going to take that into consideration and then finally in D we have the kind where you're retaining services from the application so the API is kind of going you know what when I open a file I could call you back with some of these things so it's going to retain the services that you provide it and use them whenever it would have used them in the middle of processing some of its various function calls and finally we have flow control and flow controls pretty easy to imagine pretend these are just stack traces and flow control is just you know a mesh with flow control anyways it's like who is calling who is it the case that I am you know the game is on the bottom of the stack then it calls into the library and that's always the way it looks that's all we get so library on top game on bottom or do we have a situation where hey the game which you know was originally calling the library now gets called back and there's sort of library in between game on either side of the stack then we could get you know totally crazy and say well a lot of times then the game has to call the library something for so we can keep ad infinitum if we allow this kind of flow control stuff to happen we can get these ridiculous stacks where it's like I call the library ladder calls me back I call the library maybe it called me back one more time now this is obviously you know kind of a negative thing because the more this happens the more kind of complex it is to visualize in your head what's going on your relationship to this library and furthermore it can be really kind of nasty where it has to call back one of your classes or you have to have void stars which tell it what the you know the data is that you're going to need inside there because you no longer have your scope so there's a lot of complexity when you started to do some complicated kinds of flow control that aren't just a and the files and the code snippets for that or this is the most basic kind like we said before I just call a function and it returns something everyone's happy B is the slightly more complicated version where it's going to call me back so you know I do open file and I get a callback for it C is just up here with because it's the same as B if anyone thinks that C is not the same as B definitely rethink that because hey this is just a function pointer right a virtual function is just there's a V table somewhere so if you're inheriting from one of the api's classes that's exactly the same thing as setting some file callbacks and then finally we have you know hey you could use exceptions or someone transfer flow control but hopefully that's not like a big part of any licensable API so here's the recap I'm great we have granularity and essentially what we do with that is we trade off flexibility for simplicity the more coarser grain the granularity is the simpler the API is to use because there's less calls less things for the user to get wrong said etcetera but we give up flexibility as we go up there we're kind of at the point where they're really kind of out of control you know they don't have the control they're kind of just telling you what to do in the API does it you know entirely the way that they want with redundancy it's really a trade-off of convenience versus orthogonality if redundant if your thing is very redundant then you're going to be very convenient cuz there's tons of these api's to choose from for every different thing to do but you're not very orthogonal right this it's kind of a little harder for the user to keep in their head because there isn't just one way of doing the things they're going to do there's lots of different ways with coupling there really isn't much of a trade-off it's like the API we'd like it to be as decoupled as possible because all it's doing is putting restrictions on what the programmer can and can't do is there's really no benefit to that one retention is kind of a synchronization versus automation thing where it's like if I retain a lot of data then it's kind of hard because the the user has to synchronize all that stuff and that's bad so you know immediate mode things are better because there's no--there's and that synchronization involved where they have to constantly keep you updated but you know you may lose some automation there because if you have all this retain data then the app the API can maybe automate things for you that it couldn't otherwise and finally flow control again not much of a trade-off there if you can get away with just always having the game in control and it calls the app in it returns to you well that's always simpler because you don't want to have to worry about these like kind of deep callback situations or how you get data down through through the library to you on the other side and so on and the final thing that I want to mention about that is just that looking at all those trade-offs they're not necessarily constant throughout the course of the integration when I first integrate component into my game I'm probably looking for very low granularity so I'm looking for even a very coarse-grained granularity approach and a lot of retention because I just want to do something like say hey load some characters off disk and animate them walking around and like free them later like that's kind of the level I'm looking for when I'm just like pre-production or you know doing my first integration into the game whatever but as I get to the end of the project I typically need the reverse right I need a lot of control so I fine-grain confine granularity in several places that I really need to manhandle and I don't want a lot of retention because I've built all these data structures that say the way my game works and the less of that I have to mirror on your side I'm the API side the better so is everyone clear on that is there any questions hopefully but okay so what I'm gonna go through now is I'm going to go through some looking at actual code snippets that you know they're they're not exactly the same as as Co snippets in a game but they're very very similar to it so we can look at what happens when some of these things are not at the proper level that they should be just to give you a feel for if you're designing API how you should be looking at it or if you're evaluating an API looking at what the consequences of that API are so obviously I've I've changed this so this isn't none of these are specifically somebody's API they're just like very representative of the standard ones that are out there so first thing let's look at game provided services so in this case I kind of alluded to this in earlier slides here's a case where I want the the API I was just kind of calling this read file thing in the API and I was getting back whatever the thing is that it gives me back some kind of object I'm going to use I want to stop it from from touching the disk you know I'm going to manage that because I'm reading from my own database format or something like that so typically what most api's do a booming they provide this at all which hopefully they do is we get something like snippet B which is that I set some file call backs which is basically just me saying I'm providing like F open F read and so on now so just call me when you would have called those and the problem with that not only is it a problem for flow control because hey now I'm you know getting called back by the by the API but it's also a problem for coupling in kind of a non-obvious way and the reason that there's coupling here is because you've bundled the concept of reading the file with interpreting it into this object that you can use so I've actually kind of smacked two separate things together one of which is loading and interpreting an actual like chunk of data off the disk and the other is interpreting it so even if I allow the the user to do those operations I haven't given any control over when they happen because they're still gonna happen right at this call and there's no way to separate those two things out so and you know if the user wanted actually separate them out what they have to do is make famously complicated versions of these that do like caching behind somebody's back or you know God knows what's gonna happen so the much more decoupled way in which most like very very few api's do but some do is to kind of give you the ability to just pass in some file of data it's like okay I already read it I want you to go ahead and interpret this thing into a thing for me right and that's kind of more what you want to see because if you want this kind of control typically you want something that's a lot more like this where I'm just feeding it chunks and it's interpreting them into the type that I want now is that the most d couple we can get you know it's not you look at that and you go well this is still something that's kind of owned by them right the thing that's coming back like came back from the API and I had no controller like that's got to have some memory somewhere like something's going on here at the very least let's pretend that that file data that it's interpreting is compressed in some way so at the very least it's got to decompress at first before it can be used so it's happening inside this call as we you know the API is allocating a buffer or decompressing into it and then returning me a pointer to some part of that well I could do couple it further right I can go like this which is to say that I want it to decompress this raw file data for me into file data and then it can do it can you know make the thing for me I can use and then I can get rid of this file date because I don't need it anymore but the problem with that is hey you know what it's still allocating memory so then I definitely get right back to where I was before where I have to then give it more yet more callbacks I you know I was trying to eliminate those before but now I'm right back to it so I got to let it allocate the memory for me so finally we get to F which is more of the like properly decoupled version which is to say that here now I've got you know a four line version of what was a one-line thing before but now I have complete control right I get the size that I will need for this thing I malloc that or you know new it or however you want to get it call your own special allocate or do whatever you want then I decompress it and then I ask you to make the thing so now everything is entirely within my purview here and all its doing is translating you know a pointer to something else now we can still get decoupled ones further step this one is is not necessarily necessary all the time but for certain types of api's it's crucial and that is this is the snippet that we had but what we really might want to do is something more like this where we're saying you know what I don't really even want you to require me to call one of your functions before I can use some of this data I don't want your 8 you know however your PAC data format works to influence that so what I'm what I want to do here is I wanted to say hey you know what give me you know make one of these things and I'm gonna read it in however the hell I want to read it in you know and that's kind of important because maybe I then want to go ahead and control exactly where that thing is placed with my own allocators and read it directly in and own by the time to get to H we realized you know what we didn't need the API at all for this process right simple to line thing if the data is transparent to us we can just do this and have complete control just like it was something in our game and we haven't really affected the way the API functions at all right all the rest of the API can still work exactly the same way but now we've kind of removed it entirely from this process so again I'm not trying to suggest that the difference between a and H is like always use H or anything like this I'm just saying like here is an example of the huge spread of ways in which we could do this one thing and typically you do end up wanting to do this one quite a bit in an average game for you know api's that are tightly integrated so a is good and a lot of times the user will call that but H is also good and you don't want to be in a situation where the only thing you have is a so let's look at another common thing which is parameter redundancy parameter redundancy like I said before is the ability to kind of like call something with two in two different flavors or multiple flavors and so on in a I have the the original version of the api's function which inverts some transform and let's pretend that the transform are passing and the inverse we get back are both in there like that's their data type that's their object or whatever it is well the problem is when we start realizing that hey we have our own transform type and we got to kind of pass that in we end up with with something like B where like maybe I have like a floating point vector of position and floating point vector that's the rotation and now I've got a call the API to bundle it up into one of their things and then when I'm done with their function I got a call copy transform to kind of you know get it out again to into my format and that can get really heinous if my version but you know the the the object type that I'm using for a transform also has this process that has to happen then I've got to get it out of mine put it into theirs call their function get it out of theirs then put it back into mine and like that seems kind of silly cuz like all we were doing is this and now we have to do all this so like you know it seems like kind of a contrived example but at the same time like I see this all the time in actual code like this sort of thing happens all the time it's not like this kind of random thing so it's like you're always currying back the quick back and forth between these types and ends up just being this kind of mess so really what you want to do is get as close as possible to D which is where maybe the API has several versions of this function because it knows that everyone has their own format for position and orientation so let's try to provide as many of them as we can to reduce the chances that they have to do this kind of ridiculous dance so here we're kind of like saying well you know if you've got a quaternion well you know pass that directly into us and we'll just kind of like take these to modify them and pass them back out to you okay so let's look at the transition between a coarse grained a coarse grain operation and a fine grain one in code snippet a I've got kind of a retain mode thing going on here where the app has some some kind of object called a node which has some stuff in it and I'm asking it to update it and then I'm asking it to render it to operations on the node I'm highlighted this one because maybe all I really want to do is change the way that operation works the rendering is great all the rendering is happy but the updating is not doing it things I want so in code snippet B I've expanded it out and since I'm not using the node structure anymore as a developer I now have to duplicate all of the stuff that that thing did and I'm just kind of making up some stuff that it did here the update part did those three things in the render part did those five things as you can see as a developer I'm a little sad because I didn't really want to know all that render stuff I didn't want to any of that but I had to do it all because I wanted to stop using that nude thing so I could get control over the update part well that's not very preferable and if we look at that what we really wanted to do is just kind of have the exact same call that was there before but now instead of using the bundled node type I just passed the parts of that node type that I wanted and this is a very easy thing for an epi to do but unfortunately almost none of them actually seem to do it a lot of times when they have these kind of bundled constructs they don't actually provide the exact same function in an unbundled way which is really kind of unfortunate and similarly I could look at a further kind of granularity option which is if they did want a little bit of control over one of the processes maybe I even offer another level of granularity in between those two where it's like not only do I allow you to kind of go from node to non node so I can break up these two things and not have to worry about this one but maybe I also allow you to do part of the render in me now and pass in this additional part so like I sort of take a little bit of the process away from you but you still do the rest of it and not have to worry about it okay and now here's the final example we're going to look at which is kind of the retained mode problem the typical retained mode API problem that we get into when you don't provide some of the ability to do things immediately so I've used the physics engine here because this is the they're kind of the thing that's most typically known for being very retained mode heavy snippet a is kind of what you would typically see in a physics API it's like I'm going to create this rocket and this pole that's stuck in the ground and I'm going to create a joint between them anyone who's ever played rocket jockey knows where this comes from and I'm gonna kind of like constrain the rocket to the pole so that as the rocket goes it kind of is circling around the pole and then I call simulate and it kind of just all magically works and everyone's happy my rackets flying around is great but what happens unfortunately in game development is you know that that's not really very representative of what happens what typically happens is I've got something that's like some sampled thing that's very fine-grain like is the user pressing the X button and what I have to do if this is what my API looks like is I gotta go oh well if they're pushing the X button then if I'm not already having one of these hook lines in there I've got to make one and if I had one and they weren't pushing the button then I've got delete it and remember that I deleted it and then I can simulate so what I'm essentially doing there is I'm writing almost the like diff for every part of my game where I'd if they're retained mode version with what I actually know to be going on in my game and hopefully I did the diff properly and made all the right calls to like change it up right that is really heinous when you end up working in applicants like I think probably most people in the audience have had to deal with this at some point they've ever used a heavily retained mode API what I might try to do is turn this ugly snippet that I don't like into this snippet I mean this is all I was trying to do all I'm trying to do is say hey you know what do a joint between the rocket and the pole when the X buttons down like that's what I was trying to do so instead of writing this I want to be able to write this and that's the real difference between immediate mode and retain mode it's like immediate mode allows you to procedurally do something so you've got code code is very powerful you're making decisions in that code and you want to be able to just dish the output of that code directly to the API have it use it you don't want to have to go through a data intermediary which is the retained part of it which you constantly have to worry about how to differentially update and the thing that I'd like to remind everyone here because hey you know that I can only show so much on a slide is that having a do joint call like this immediate mode is much more powerful than if I say had just a boolean that I could turn on and off in a retained mode joint and the reason for that is what if I have no idea what pull the guy is gonna catch this time well like I don't want to have to do stuff like create speculative joints between all the rockets and all the poles and then just turn the boolean on and off so I can have the nice immediate mode feel right I don't want to do any of that stuff so the idea behind immediate mode is I've gone through the code I figured out exactly what I want to do I just want to call the API with it right there and I don't want to have to worry about having set up a retained mode structure previously which will allow me to have that code look the way I want which is what boolean's in an in a retain mode thing would do for you so that's not a substitute so finally you know aetna hopefully giving you idea of how those five characters is kind of work when you look at the code what ends up happening I think with most API is not that there's that many that have tried kind of to do all this stuff but when you when you look at them I've talked some people have actually done a bunch of this stuff and they kind of seem to concur with me so when you kind of follow these either by intuition or because you've sat down and actually looked at with all they they all are the api's that work the best for reuse across multiple projects that are different very different from each other you know wide reuse kind of situations that people have a pleasant time with is that when you optimize these five paradises you get an API which kind of has gradual tiers to it right there's like lots of different kind of ways that I can access this API and I can gradually move between them as I need to they're highly decoupled so I may even have multiple tiers of stuff that don't even necessarily have to like kind of couple to each other they're very separated if I decide to start getting more group you know a finer-grained in one area then I don't have to drag down everything there they're all kind of like staying at the level that i roughly want they have no retain mode stuff at their bottom so in other words at the very very finest level that I can call this API it is not retaining data at least not that I know about if it is it's behind the scenes it's caching its stuff they're doing for performance but I am NOT responsible for doing that caching for them and as you kind of go up through the tiers maybe there is some retain mode you know sometimes I do want to just say great rigidbody cravers by a create joint there's nothing wrong with that but by the time I get down to the part where I'm I'm asking the API I want control here I don't expect to have that be happening and then finally I'd say that you you pretty much never want to be in the situation of having the API dictate the flow control view because most of the time there's there's really just no no reason for that so that's kind of what I see is like the way api's look when you've really kind of got this stuff dialed and granted like you know like I sang with granny one in grade two I think we got a lot of the stuff in grading too but you know I look back at it now obviously and there's tons of stuff I would do differently so it's not like I'm claiming that you know I've ever made an API which does all these things perfectly but it definitely yes as you kind of feel like you're making API is better I feel like they go towards this and I think over the next few years we'll start seeing API is that kind of if they can do this we'll kind of get more towards that kind of perfect point where all these things are working together properly so you know some of you may may not have one spend a full 50 minutes talking about this stuff so you know right now I've summarized it all into just like a minutes worth so you can get it all right now if you were designing an API or if you're about to evaluate an API these couple slides should give you everything that you need to know to cheat on the exam the first and second thing is that you need to know and this is kind of obvious but I just wanted to state it because most of this lecture is about the more detailed stuff is just that always right the usage code first you know I mean when you sit down to design an API always write all of the like examples of using it that you can think of right those first don't start by like opening up an H file and start writing class declaration or something like that start by writing you the user go and furthermore if you've got a game sitting around which a lot of you do like pretend you're integrating it like pretend you have the magical API that does everything you want go into that game and integrate it through and look at what you came up with that's the first passed API that's the best first pass API you're going to get because all of your intuition is enabled there naturally and you've got all the constraints of that game already playing into it so you've got a really good first example there and furthermore if you're evaluating one you may think that the best thing to do is go read the documentation of the of the tools that you're you know potentially evaluating placing don't do that yet pretend you have the perfect one you want pretend to integrate it into the game you've got for a day or two look at what you came up with and now when you evaluate those components from the different vendors go how close does this match what I'm going to do like don't think in their terms first thinking your terms first and then as you evaluate your API is go like which one of these things you know links up with me so anyway most of lecture was not on that but I figured it was very important to mention because a lot of people won't do that so now I get this stuff that hopefully this lecture has argued is is the right thing to do the first thing is that any kind of retain mode contract of this API puts forth I should be able to do the exact same thing in immediate mode by just calling a function with the things that the retain mode structure had in it right and that was the thing about the update node render node thing I should always be able to kind of have the non node version of those so I can just call them and what that does is that allows you to transition from the retained mode to a more finer controlled immediate mode when you need to if you don't have those then when you need to transition you're gonna have to go oh my god like what was that no thing doing I don't even know I gotta go learn about that whatever hmm second thing anytime there's callbacks or inheritance there should be an equivalent way to just make an API call that doesn't have to use either of those things if you ever see something that requires inheritance or requires you to use callbacks then that is giving up both flow control and it is coupling you so you should never see that if you see something like that that's a red flag third thing no API should ever require you to use a specific data type of its own if you know full well that your game and probably every other game in the universe has their own version of that data type like the last thing you need is for you to go license three components from three different vendors they all use different vector types and you've got to use all three different vector types in various places of your app because that's what they expect like that's the last thing you want to have happen number four any API function that you would not consider atomic for purposes of writing your game if you think there's any way you might not consider that thing atomic at any time during integration then it should be able to be replaced by between like say two and four different api's that replace it that are more granular right and that doesn't count maybe access or functions but just you know the actual physical operational calls because if you look at something and you go you know what that thing looks kind of a little high level to me and I don't see any way to break it down further then that's a really good example of where you know some point deep in the ship process you're gonna have to hope you can get one of their API developers on the phone to kind of put in some backdoor access for you or something like that and that's really bad and the final four things any data which doesn't have a reason for being a Paik should be transparent if you just have data struck at the API using four things those things should not be opaque right you always have the choice as a developer of not touching the data structures of the API so if you're concerned about making you know you know touching a kind of internal structure or something like that and having that break when the API gets revved you can always decide not to do it right there's nothing forcing you to go look at you know some of the structures that the API defines but when it comes down to ship time you may find that you want that access to do some things in certain places in your app and if they aren't exposed to you then you've kind of got this problem where you'd better hope the API has actual calls that do the sorts of stuff you need directly or you're going to be in big trouble so the idea is you should look through it and while you shouldn't expect to use all of those things you should try to make sure that they're there for when you do need them you should never have to use their resource management which means like memory management file management string management and so on and you should never have to use their file format and you should always be able to get full source code to the runtime so those are pretty simple [Music] [Applause]
Info
Channel: Casey Muratori
Views: 36,579
Rating: 4.9829302 out of 5
Keywords: API design, component design, C++, programming
Id: ZQ5_u8Lgvyk
Channel Id: undefined
Length: 49min 55sec (2995 seconds)
Published: Wed Jul 30 2014
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.