We're sorry but this page doesn't work properly without JavaScript enabled. Please enable it to continue.
Feedback

Porting Quake III to F#: A Journey to Functional Programming

00:00

Formal Metadata

Title
Porting Quake III to F#: A Journey to Functional Programming
Title of Series
Number of Parts
170
Author
License
CC Attribution - NonCommercial - ShareAlike 3.0 Unported:
You are free to use, adapt and copy, distribute and transmit the work or content in adapted or unchanged form for any legal and non-commercial purpose as long as the work is attributed to the author in the manner specified by the author or licensor and the work or content is shared also in adapted form only under the conditions of this
Identifiers
Publisher
Release Date
Language

Content Metadata

Subject Area
Genre
Abstract
FQuake3 is a project started by Will as an attempt to port id Software’s Quake III Arena to F# and to figure out how functional programming can be applied to game engines. The project is less than a year old, and has been worked on by Will in his free time. The talk will discuss Will’s journey to the founding of F# and why he started this project. The project structure, demos, code examples, and comparisons will be presented along with a live code example of how to port a C function to F#.
Game theoryComputer networkSoftware developerFunctional programmingAndroid (robot)Run time (program lifecycle phase)Flow separationNeuroinformatikBeer steinMultiplication signForm (programming)Right angleWindowAndroid (robot)Flow separationProjective planeSoftware developerGoodness of fitPoint (geometry)Enterprise architectureGroup actionVideo gameProcess (computing)Real numberFunctional programmingBitLibrary (computing)Open sourceDifferent (Kate Ryan album)Functional programmingSoftware testingoutputConfidence intervalDemo (music)WritingTouchscreenSoftware frameworkVariable (mathematics)Computer animation
Game theoryInteractive televisionKey (cryptography)Fraction (mathematics)Scale (map)Interior (topology)Convex hullCartesian coordinate systemCore dumpMathematicsVolumenvisualisierungShader <Informatik>PhysicsTwin primeForm (programming)TorusCodeDisk read-and-write headSoftware developerQuicksortCASE <Informatik>MathematicsLogicPosition operatorSign (mathematics)AreaGame theoryLevel (video gaming)Escape characterFigurate numberInteractive televisionComputer animation
Cartesian coordinate systemForm (programming)Game theoryMathematicsIRIS-TProgramming languageFunction (mathematics)Code refactoringCompilerKeyboard shortcutService (economics)Interactive televisionInversion (music)outputStreaming mediaString (computer science)SynchronizationComputer fileContent (media)Functional programmingBound stateIntegerVector spaceEmailDefault (computer science)View (database)Number theoryMereologyCodeMultiplication signDemo (music)Game theoryClient (computing)ImplementationEntire functionLatent heatPosition operatorVariable (mathematics)Service (economics)Level (video gaming)Computer fileSound effectType theoryInteractive televisionSurfaceFunctional programmingAngleSign (mathematics)State of matterCuboidSet (mathematics)Endliche ModelltheorieProgramming languageScripting languageMathematicsExpressionSystem callComputer configurationLogicFrustrationPerformance appraisalOrientation (vector space)Local ringBound stateoutputView (database)Function (mathematics)Core dumpParameter (computer programming)CompilerPolygon meshTupleData storage deviceComputer programmingKeyboard shortcutBootingRight anglePointer (computer programming)VolumenvisualisierungFunctional programmingComputer animation
MereologyAsynchronous Transfer ModeFunction (mathematics)Bound stateOrientation (vector space)Singuläres IntegralCartesian coordinate systemView (database)Local ringCode refactoringFunctional programmingElectronic signatureSubject indexingTrianglePlane (geometry)SurfaceVertex (graph theory)Price indexPoint (geometry)Sign (mathematics)Data typeTelephone number mappingStrutContext awarenessBinary codeSurfaceType theoryIdentity managementEndliche ModelltheorieFunctional programmingRow (database)Field (computer science)Orientation (vector space)State of matterLocal ringEntire functionSystem callParameter (computer programming)PolygonNatural numberElectronic signatureRight angleCasting (performing arts)Statement (computer science)Matching (graph theory)AbstractionCode refactoringFunctional programmingCuboidTelephone number mappingExistential quantificationImplementationBitDataflowArithmetic meanContext awarenessSound effectSemiconductor memoryTriangleWebsitePoint (geometry)Data structureFactory (trading post)Fraction (mathematics)Revision controlComputer fileMessage passingCASE <Informatik>Service (economics)Structural loadMetropolitan area networkComputer animation
SurfacePattern matchingView (database)Social classAbstractionClient (computing)Game theoryPhysical systemOvalDefault (computer science)System callError messageBound stateFunction (mathematics)SpacetimePointer (computer programming)Data typeTexture mappingCartesian coordinate systemSystem callImplementationMultiplication signProcess (computing)Functional programmingFunctional programmingLibrary (computing)Object (grammar)BitComputing platformBootingBoilerplate (text)Line (geometry)Projective planeSoftwareRevision controlData structureData conversionType theoryOrder (biology)Parameter (computer programming)MappingMacro (computer science)Natural numberPattern languageNumberClient (computing)Metropolitan area networkComputer fileDifferent (Kate Ryan album)SurfaceResultantVisualization (computer graphics)Endliche ModelltheorieDynamical systemPoint (geometry)Function (mathematics)Game theoryCode2 (number)Physical systemMathematicsProduct (business)Information securityLogicStandard deviationSpacetimeLatent heatField (computer science)Module (mathematics)Price indexGoogolWeightInclined planePointer (computer programming)Subject indexingComputer animation
Functional programmingLogicCuboidCodeFunctional programmingSet (mathematics)Physical lawBitLocal ringMountain passComputer animation
MathematicsVector spaceDegree (graph theory)Module (mathematics)Cartesian coordinate systemAngleSinguläres IntegralStreaming mediaReading (process)String (computer science)Shader <Informatik>ParsingSurfaceEmailFrame problemLengthEquals signEndliche ModelltheorieSoftware frameworkWindowPhysical systemSample (statistics)ParsingUtility softwareLibrary (computing)Degree (graph theory)Data structureFunctional programmingFunctional programmingWave packetNeuroinformatikNoise (electronics)LogicRotationBinary codePoint (geometry)ImplementationMultiplication signMathematicsType theoryFrame problemMeasurementFluxBoilerplate (text)Fitness functionCalculationFraktalgeometrieAngleLine (geometry)DemosceneParameter (computer programming)Overhead (computing)Cartesian coordinate systemExecution unitStreaming mediaSoftware testingProduct (business)Service (economics)TriangleMaß <Mathematik>Endliche ModelltheorieNormal (geometry)CodeTypprüfungParsingFigurate numberSystem callWritingDynamical systemLengthComputer animation
Run time (program lifecycle phase)MathematicsPhysical systemGame theoryConvex hullBeer steinInclusion mapLink (knot theory)CodeDisk read-and-write headReal numberData storage deviceWeightWebsiteLine (geometry)TouchscreenMachine codeKeyboard shortcutTriangleCodeProjective planeLogicType theoryMereologyLibrary (computing)Utility softwareExterior algebraArtistic renderingFigurate numberPoint (geometry)Multiplication signSpeicherbereinigungLink (knot theory)Data structureEndliche ModelltheorieSystem callOpen setRoyal NavyAlgebraic closureFamilyBitComputing platformAreaRow (database)Operational amplifierLevel (video gaming)Computer fileComplete metric spaceRing (mathematics)Computer architecture
TwitterEmailBoilerplate (text)Point (geometry)MappingData storage deviceQuicksortSemiconductor memoryMultiplication signFunctional programmingFibonacci numberCodeSpacetimeType theoryTheory of relativitySummierbarkeitWeightBitVector spaceService (economics)Functional programmingSequenceCycle (graph theory)Real numberElectronic mailing listSet (mathematics)Process (computing)Computer animation
Computer animation
Transcript: English(auto-generated)
Hello? Well, thank you for joining this talk. This is porting Craig 3 to F sharp. It's my journey to learning functional programming. I'll tell you a little bit about myself. I'm Will Smith. I lived in Nashville for almost 25 years. I've recently just moved
out to San Francisco to join a startup called Tachius. And so there's like, moved there like four weeks ago. And now I'm like flying out here. It's my first time to Scandinavia or even Europe or even a different country in general. So it's like there's a lot of
stuff going on. A little bit. So I'm going to tell you a little bit about what I do now. I use as my job, I use Xamarin and F sharp for Tachius. This is what I'm going
to be doing. This is my full-time thing. And I'm really happy to be doing this. But so anyway, what is this talk really about? Enterprise! Quake 3, we can use it for the enterprise. We can leverage all the AI and do all those computations that all those enterprise folks just want to use. You can leverage any framework using it.
You see, look, we're really taking advantage of everything. No. I'm just kidding. It's not at all what this is about. So what is it really about? So I started a project.
I called it F quake 3. I don't even know if it's even like a good name. I'm not very sure if it's functional or F sharp. So you can get an idea of what's probably behind it. But this is like my journey to learning functional programming by doing this port.
So first thing I want to talk about is that with this port, questions that will probably be asked is like how's the performance? How much has been ported? Well, performance
doesn't matter because first I need to get everything working. If it doesn't work, it has to work. But the amount of porting I've done is only like 5%. This is like over a decade's work for somebody to do. But I'm trying this because I really want to see
how functional programming can be applied to something, you know, big. It's not something that not Fibonacci, not Fizzbuzz, quake 3. So this begins my journey of like how did I actually get up to this point? So before I even did this or even started learning functional programming, you know, I did some contributions to like a few projects that
were in C++. It was okay. The community and how people acted, it was all right. But I kind of moved away from that and I'm like, all right, I'm going to learn C++ 11. It's got Lambdas, that functional thing I hear about. They got that. So I decided
to learn it, but then it ended up not really working out for me. Things are just not expressive as the way I wanted it to be. So I moved away from that and found this project, this open source project that a guy named Frederick did. And I did some
contributions to this. So RTCW co-op, if anybody doesn't know, it's return to castle Wolfenstein cooperative so you can play the single-player campaign of RTCW with your friends. And so this is based on quake 3 engine and I went and
just made some contributions to it and it was written in C. So things I realized, C was actually kind of elegant compared to C++. There's this separation of like data and behavior. Things started just kind of feel right and developers were actually kind of friendly, which is like, okay, well, I
don't feel like I'm being shoved against the wall here, so it felt nice. But I started to do something interesting. I tried to write C functionally. It's like, let's just make a variable. I'm just not going to change this variable. Let's just see what happens. And it was actually kind of interesting.
It started to feel right and I realized that I'd probably need to find a functional language that actually embraced this. And so I found F sharp. Out of all the functional languages that are out there, I found this because it fit my requirement of it being functional first because I still
want to use mutability for actually interacting with existing libraries that exist out there. So I have to have that. That's just what you have to do. You have to update the screen. So you have to have some
form of mutability there, but if you can push everything to be as functional as possible, that's what I was looking for. And so F sharp kind of felt like, okay, this is why I wanted to use, but I needed to make sure that it was okay. I started doing some mono research because I always hear about the mono runtime being, oh, it's bad, it's slow, it does all this, but I started doing research
on it and it really wasn't as bad as what people really made it out to be, and I did some examples of my own to see if it actually fit. And sure enough, I felt good about it. I felt confident. And I did all this stuff starting out just in Ubuntu. So I started learning F sharp and using mono on Ubuntu before I even moved to OS X or
Windows using it. So I actually was like, okay, this is actually not as bad what people say. So I can also use this at work. So F sharp for Xamarin, iOS, and Android, like right now, I'm now doing F sharp and iOS. And of course, F sharp
reminded me to see with data and behavior being separated. So, okay, that's cool. Made a few example projects using F sharp. And then that's when F Quake 3 was born. So I wanted to do something real. So now I want to give you a demo of this. I don't have the CD key. Oh, well. Okay. So here I am in the
game. My name is F sharp Quake 3 guy. So this is actually running F sharp code. There's still like a huge portion to see there, but there is a good portion using F sharp code. Well, how do I make you believe that? So
let's change something then. And my weapon was modified on the fly using the F sharp interactive. So here I can actually change anything and do
whatever I want. And this is a good way to actually learn what is actually going on, like, just in the code to figure out, you know, how can I do this, how can I do that without having to recompile the whole thing and running it. I can do it here on the fly and do whatever I choose. I can only do it for the weapon position in this case, but I just see that there's some sort of future in doing stuff like this.
That's where that's what that's why I feel development is going to move towards. You'll be able to write something immediately and see the feedback, like, on the fly. So what else could I do besides have this weapon that's really far out? So let's go into third person.
So there's a head. And now it's up there. Cool. And now
there's no head. And now there's Don Sine. So let's go defeat the
other Don Sine that's running around in this level here. Where did he go? Oh, there he is. Come here. Goodbye. So this is a
little simple example. We can take it a little bit further in some sense. So I want to write some logic on the fly here and see if I can do it correctly. Let's see. Cosine. Oh, my gosh.
What? He must have escaped. Okay. Now he's completely
wigging out right now. I have no idea what's going on. I just
want to slow him down just a little smidge. Okay. Now it's starting to feel a little bit better. Oh, baby. There we go.
Oh, thank you. So yeah, we can do all kinds of stuff. Let's just use a simple, you know, we got the time of the client game. Let's do a cosine over it. So that's why it gets the
movement doing this. But this is just a simple example proving that, yes, this is running F sharp code. But there's a demo. So back to now, for those wondering, like, how did
this actually work? Like, how did this, like, how did I get this working? Well, there's a thing called the F sharp compiler service, or FOSLAN. So expose additional functionality for employing F sharp language bindings. And
has a pretty simple API to use in really good documentation. And so this allows you to embed the F sharp interactive and anything that you want and just be able to evaluate expressions and script files of any sort. So to give you
the example of how I use this, so looking back at the weapon position that I modified, well, here's the function. It takes C game and returns a tuple of two vectors. So one is position, the other one is angles. That's it. And these types are all completely immutable. So there's
no side effects going on in any of this. So in the file, I have to set a mutable variable. Just think of it as a FSX. And really, it's just going to contain the implementation of calculate weapon position so I can change it at any time. Because it's going to look and
actually call FSX. And so within the same file, I choose what code I want to compile with and the code that I actually want the FSI or F sharp interactive to run. And so you see nothing's interactive, but in the else, the mutable, that's actually going to be compiled.
Whereas in the actual implementation here, if the interactive, this is where all my logic is. And in that same implementation, the one that's actually outside the interactive, that's when I actually call the FSX to call the implementation. And this is, I wanted to show this
because this is verbatim from the F sharp compiler service documentation on how to actually boot up and embed an FSI. It's pretty simple. I just copy and pasted it and it pretty much worked with one modification to the ARG, the FSI EXE. I'm choosing which FSI that I
want to run that actually works with Mono. And so how I detect file changes, it's just, all right, get the last right time. All right, if the last right time is different,
well, we'll just evaluate it. FSI session eval script weapons FSX. And that's it. And if there's an exception, I just eat it. I really don't know what to do. I just eat it. And so this is just where it gets set. So calculate position FSX. I set that with a new implementation of whatever calculate weapon position I
have. So that's an example of how I got that working. Now, moving on to, really, this is, like, the core thing I really, like, want to present is, like, do you really get benefit out of using a functional, just doing
functional-style programming in general, like, in doing this? And I want to tell you yes, and I want to prove it to you. So benefits of functional are immutability. So variables, well, really, they're not variables anymore. They're really just values, and they just cannot change. And you have referential transparency, and that just
means whatever input you give it is the same output that you get. Same input, same output. And that's, like, that's what that means. So now let's look at some C. So here's a function called call local box. So if you're wondering what this function does, I'll tell you, but it's
actually kind of irrelevant. We're just really looking at where functional actually helps you. So call local box really just says, whatever my view frustum is, do I actually need to draw a model or a mesh that's in my frustum, yes
or no? And that's just what this function does. So looking at this, there's some local variables here. We got, you know, parameter called bounds. Okay, cool. So now we're in the implementation of this function called call local box. Now you see some things here.
Where do these come from? They weren't passed in. They're not local variables. They're global. Who knows where they're defined? But they're using them here. So why is that actually bad? Well, I'll give you an example. We're going to start out by looking at this specifically. TR.OR. So you get familiar with what this
is. TR really is the entire, like, render state of the client. And OR is really orientation. Orientation of what? It doesn't care. It's just a place to store orientation for it to use anywhere it wants, which is actually not that good. So we're going to figure out,
like, what is going on there? So looking at where call local box is getting called, here's another function called call model. Got some local variables and some parameters. Okay. And the implementation of call
model, there is no sign of TR.OR being set anywhere. But yet there is call local box being called, but we don't know where TR.OR is actually getting set. We still don't know yet. But we do know that now call local box depends on TR.OR getting set. Moving on, we
go a step further up. There's another function, add call model, but there's still no sign of TR.OR actually getting set in this function. So we went, like, one, two, like, a few levels out, and there's still no place
where it's actually getting set still. That makes call model be dependent on TR.OR. Okay. We go up another level. Sure enough, there it is. And this is actually where it gets set. You actually don't know if it's
like an out pointer right here, and that's where it's going to get set. The comment tells you, but you shouldn't always rely on comments. And sure enough, there it is, add MD3 services. Now, why in the world could they not just pass that in? Why do they have to set it to the global? This makes add MD3 surfaces
not reusable. And it's not even an abstraction at that point, because it relies on TR.OR to be set somewhere. And you didn't know that. How are you supposed to know that? That means you have to dig through the
implementation of what add MD3 surfaces does, and it calls call model, then it calls call local box. Okay. It does all that. But you have to dig through it and find out that it's setting that. It's not an abstraction at that point. It's broken. So it depends on that.
Okay. And the function signature should really tell you what you need to do. In this case, it really doesn't. It doesn't tell you that it needed that at all. So what am I doing in F sharp when I'm doing this port? So here's our call local box again. And
notice that I actually have parameter called orientation. So I'm actually passing that in. I'm not bringing any globals into the scope of this at all. I'm actually
just passing as a parameter. It tells you what the dependencies are when you do this. Going a little bit further up again, we look at call model. And you start to see that, okay, there's renderer. That's that R right here is really that TR. And I'm just passing
the entire global state in. So that means I'm taking that unmanaged type, TR, copying everything over to immutable type on F sharp that is just a record. And then passing it to this function. So of course that's costly. But that's like the proper way to do it right
now. But then you start wondering, do I really need to pass in the entire global state of this? Do I actually need to pass all that in? And sometimes you may find out, wow, I actually don't. And that's when you can start refactoring stuff. And it becomes a little bit safer because you have immutability. It's safer than like, hmm, if I try to move this or
make it shorter, will that affect something else that mutated this? I mean, it just becomes like a big spaghetti. But if you make everything immutable and refiliency transparent, it pretty much goes away. And it becomes actually a lot nicer to refactor. And it actually starts to feel good. So to recap, benefits
of functional. It's easier to spot functions that have multiple dependencies. You can see the flow of data through all the functions. So when we passed, or what they should have passed is the orientation all the way through those functions because you don't know where the data was coming from or reasons why. So the story isn't even there.
So that's one benefit of functional that I found like very, very pleasing. And refactoring is easier. And having a discipline of purity by doing this, by causing no side effects whatsoever and everything is definitely too transparent, which I mean,
pure really kind of like means all those, then you get all these benefits. So now I want to get into something else I kind of noticed that was interesting. And doing this port. So there's a type in F sharp called discriminated union. And
really what this means is think of an enum with data associated with it. That's the easiest way you can explain it. Which is actually kind of interesting. So let's look at some C. Oh, gosh. Okay. So we got a switch right here. So we're
going to figure out what that is. You got face, triangles and poly. And so if the surface type is a face, then here is how it's going to get the data out of it. The same thing that we're trying to do a switch on. We're going to cast it as this type to get the data out of it. Here, here
and here. Okay. Kind of interesting. Like I've something's kind of familiar there. So what does this do? Well, man, there's the fancy switch statement called match. It's going to almost do
the exact same thing. If it's the surface is face, get the data. If it's the triangles, get the data. If it's poly, get the data. But this is all like locked in with F sharp. The C version, I mean, you're casting anything you want. You don't even know what you're going to
get. But that's just like what they did. I found that interesting when porting that portion to this actually felt kind of natural. I didn't have to think about it a whole lot or do some weird things. So just similar. But are there any scary spots? Okay. And that's what I want to go into. So let's look at surface type that we saw
that had the face, the triangles, and the poly. It's really just an enum with all this stuff. Okay. Cool. And here are the data associated with it. And notice that the very first field is surface type. So that, in memory, when we do a
switch on the surface type, not on the data, but just on the enum, if we cast it as flare, then we're going to get the data associated with it down here. And so here's like an F sharp version, how I'm defining the surface. So there's face, triangles, poly, and all that stuff. And
then the data associated with it. But C is really just not safe. And here's, I want to show you why. So here's a function. Well, not a function. So here's a struct. It's in a completely different file, a completely different context for a completely different reason. Okay. There's nothing in here
saying surface type at all. There's no type, even at the first field, which is ident, which really comes out when you read a model file, you get surfaces, triangles, and other data associated with it. But this, in particular, ident will always be IDP3. But they did something weird
when they loaded these model files. Oh, they decided to change it. SFMD3. Now, so that means they're using this struct that was in a completely different context, completely different file. Now they're going to make it be used as
a surface type, which is absolutely dangerous. And actually, it was stuck on this for probably about a day, because I didn't, I couldn't find where the data was coming from. It wasn't, it was defined in another file in a totally different project?
Oh, no. So they decided to, you know, hey, we can just change it and just use it. How clever. But in this, I thought it was kind of interesting to see poor man's discriminated union, poor man's pattern matching, and see where things actually start to kind of feel right, and how this port to F sharp
actually started to feel kind of natural. So moving on. Now, this is, this is something that I'm actually still struggling with, is how I'm communicating back and forth between F sharp and C. And it's
actually kind of, it can get hairy, but I'm trying to find a solution around that, and I'll get into it. So first let's look at the project structure. So we have our main executable, FQuake3. We have the original C projects in the native folder. So that's the original Quake3C that was, that was on,
that's on id software's GitHub. And we have this thing called M, which really is a, it's my own version of a mono abstraction, so I can easily call methods and invoke and do things that I want without having to do all the boilerplate that I need to do when calling mono C, when calling the mono C APIs.
And then you have the engine and client game, and this is the F sharp side. Then you have, I have some tools, so this is going to contain like parsing, math, and a couple other things. And then the launcher, which is just the main launcher that calls engine.system, and actually boots everything up.
So let's look at how F sharp calls C. It's pretty, pretty basic. In dot net you use P invoke to do this. There's platform invocation, and there's
nothing extremely interesting about this other than I have this suppress unmanaged security, which makes the call a little bit faster when hitting it. And then sometimes I'll wrap this stuff up so we're not actually hitting the main P invoke method. I'm just wrapping it. And so when calling these C
functions, I have to go in and modify those C functions with this M export M decal. And what actually are these? Well, let's look at it.
So M export just tells the C call that, hey, somebody might actually call this function from the outside of the dynamic library. That's really what that means. It marks it. And there's different implementations on that in Microsoft, visual C plus plus, and GCC, they have their own different versions
of that. So wrap it up in F define and elift define. And the same goes for M decal. And M decal is just a calling convention. There's a couple different calling conventions. I don't know why. Blame Microsoft on that, because really you just need one, which is C decal, which works on GCC, and it's really like the standard. And
so that's wrapped up in platform-specific stuff too. So that's that. So now how does C call the F sharp code? Here's an example. So this
is this M invoke new is really just a macro, and you give it a assembly name, give it a namespace, module, and a function. And the result is really the output, and the rest of it is just variadic arguments. You can pass any number of arguments you want in there, and it'll just take care of it. And so that's how I'm calling
it. There's a few things to note here that's actually kind of that I don't like very much. You see this thing called QM of, QM of, and an M object of. This is where I take these C structs, and I have to map them to a manage type, and then pass them in to this function. So
I'm doing all this whole big conversion thing of all these types, which is actually becoming quite tedious. I mean, I have a couple thousand lines just dealing with this, and it's actually like that's most of my time has been doing that, rather than writing the actual logic, which has been kind of stinky. But I'm trying to find, but I'm trying to figure out a better way
to handle the problem, and I'm actually trying to find time to do it. The M object has arg, just to quickly go over it, it's really not that important. All it does is, all right, if the type was a struct, we need to unbox it. That's really all its job is doing, because if
I want to have something that has a reference type, and I want to change it to a value type, I want everything to just still work, and so this is just like boilerplate that I just have to do. So now I do all this stuff based on conventions, so these are a bunch of more macro stuff that I don't like either, because I'm going to actually
just blow away like almost like all that eventually. But this is just what I chose, and this is just like the path that I decided to go, but you live and learn. But these conventions will look for a module, say, of vec3, and look for these particular function names called ofNativePointer
and toNativePointer. ofNativePointer takes a C struct, vec3, and converts it to a fsharp idiomatic type. And then toNativeByPointer really just takes the fsharp idiomatic type and puts it back to an unmanaged. And so I do that
all within the same module, so all that mappings are done within here and all done in fsharp. So here's just something like this is what you have to do in order to get a method. A bunch of stuff. OK. And before I get into this, I just want to show
what these, how these functions are actually getting called. Hey, look, there's col local box. We're kind of familiar with that a little bit. So I have this if0 around it. So this is the old C code
that was there. I just have it basically not commented out. I can just switch it to one and it will run the C code. Otherwise, it will run m invoke, it will run this m invoke new. So that's how it works. So any code that's in C still calling col local box
will forward that to the fsharp function m invoke new. And actually run that logic. So I just wanted to show like where, like what's going on there. OK. So now how is the math side of this stuff done?
It's like, OK. We got our vector3. Now this is me being completely naive. I didn't even know anything about math until I actually started doing this. But I really wanted to learn the math, so I decided to start building my own. It's like a good way to learn,
but probably you shouldn't use it, only I should. So our vector3 type, then we have all our standard functions for vector3, dot product, cross product, length, length squared, normalizing. But one thing to note about a feature in fsharp that I do enjoy but I try not to abuse a lot
is inlining. What inlining does is it takes whatever implementation of one of these functions are and wherever you call this, it will just actually use the implementation instead of calling the function. So you get rid of the function overhead, and you get rid of all the parameter types being copied.
So there's a performance benefit gain that you can get from that. So if you didn't say inline a dot product, and you're doing 100 million calculations on it, it's going to take you like 200 milliseconds. But if you inline, it may take you 60. And all you had to do was put inline. And so that's something that I actually kind of enjoy.
Thinking about. Here is using units of measure. One thing I actually hit at one point, I was trying to figure out, does this function take degrees or does it take radians? And I had no idea. I found out it actually took degrees, but later converted onto radians.
But using units of measure, I can type safe that. So whatever they pass in, if it's degrees, and they pass in degrees, and whatever implementation that function is, they'll take care of it, such as of axis angle and rotate around point. It's just like showing an example. It's not completely terribly important,
but it's just kind of simple. And how I'm parsing binary. So not getting into too much detail, but I just wanted to show. I'm using something very similar to fparse. I don't think you're all familiar with.
But here what I'm doing is I'm representing all the reads for parsing as really a computation. So when I actually use the logic, such as something like this, you don't actually see the stream.read, stream.that, stream.that. Really, I'm just representing this as a computation here,
where you don't have any of that noise. It's like, yes, I know I'm going to use stream.read. I know that. Here you don't have to. Just kind of write what you want, and then return the type of structure that you want back, such as P triangle. All right. Pipe three.
I want to get three int32s, and I want to return me an MD3 triangle of whatever values the three int32s gave me. That's all it is, and that's all I have to represent it as. So this is actually kind of nice. And this is an example of, like, how do you run,
like, say, the model parser. It's like, all right, parse bytes. Function call parse. Just throw in the data. It takes care of it. It's, again, not too terribly important, but I just wanted to show it. It's kind of like the main implementation of where things actually start.
So it looks very similar to what you saw before. Pipe three. We're going to get some data. We're just going to return frames, tag, and services, and we're going to give it the structure. And then here I have testing against this using a fs unit and in unit,
and just checking to make sure it actually, like, works. So now, let's see. I want to get into probably not a whole lot on this, but I do want to show something I've been working on
most of my time. It's a library that I've been creating called FairOp, and what this library will do is allow you to have inline C in F sharp, and then it will know how to compile it when you compile your F sharp code. So compile the C, generate the proper p invoke methods, and just use it automatically.
So to show you an example of this, I've now switched to OSX. This is used in Xamarin Studio in F sharp. So something of how this thing works. I define a function called init, and it's going to turn me an application,
and then here's the C that I've written. So inline C, and when I compile this and run it, it'll know to compile the C and generate me a dynamic library, and I'll get something like this. So this is using FairOp, F sharp,
and just something that I've actually learned from Matthias on fractals, which is I kind of took it a little bit further. So this is actually kind of neat. So I'm trying to solve the interop problem that I'm having and dealing with all the types and trying to figure out a really good fit,
because right now I'm doing a whole lot of boilerplate. I need something that just kind of fits and works together, and so that's why I started doing stuff like this. To show you more of what I'm doing, so this is actually using a... This is the utils library that I had,
and this... So I'm trying to figure out how to load a model file, use OpenGL 3.3, and just show it on the screen. So far, I've only gotten like some triangles that are red,
so far, which is okay. This actually took me probably about five days of like a couple hours to get some triangles on the screen. At one point, OpenGL takes column-major storage matrices, and I was using row, and it completely threw me off guard,
and I realized I had to transpose it, so that was like eight hours of like complete bashing my head against the wall. So that's an example of using FairOp. So you see I have some of the native code here, and so I'm actually trying to actually use this, and actually have FairOp integrated with FQuake 3,
running some OpenGL calls, and with this, I don't have to rely on another person's bindings. I can just use the C that's already updated, and not have to wait for the bindings to get updated for it. I can just use it here, and so that's why I built it,
so I don't have to worry about bindings, because some of these bindings, people don't get paid to do it, and so it's not going to be updated all the time, and so I needed to find like an alternative around it, and plus this will help me do the port. So that is an example of that, as of FairOp,
and so I have a couple links. So there is my GitHub for the project, FQuake3, and then there is an interesting site by Fabian,
on a Quake 3 architecture, that I've actually read multiple times to figure out what in the world am I actually looking at, and so that actually really, really helped me a lot. It's been interesting getting to this point.
I'm not sure where this project will go, given the time that I have, which I actually don't have now, but I think I have some time for questions, so if anybody has any questions, or you want me to show something else on this,
you can let me know.
Most of the 5% is done in the rendering, I chose that because I don't know anything about OpenGL and graphics rendering, so I'm like, OK, I want to learn FSharp and rendering all at once. Well, it's one way to figure out, will this actually work in the real world? So it's like, OK, it does.
So I chose that just to learn it, and it's probably going to be the most difficult part. If I can get that done, like all that really heavy low-level stuff written in FSharp, then all the high-level logic that's actually in C, in the engine, will be a whole lot easier to just port over
since I got all that low-level stuff all done and taken care of, especially cross-platform-specific stuff. Because there's probably about 15,000 lines of code per OS dealing with all the cross-platform stuff, and so I'm basically going to be like, OK, you're gone, you're gone,
replace it with an FSharp or a .net library that already exists that works on all platforms, and just get rid of thousands and thousands of lines of code with one .net library, which would be really interesting to see. But that's the path. I don't know how far I'll get.
I at least want to get the rendering done someday, at least. But once it's done, I don't know where I'll go from there, but it's definitely a journey.
Well, thinking about the garbage collector is probably very, very important, and what is actually going on there. Even right now, while things are so slow, because the garbage collector is probably getting hit a lot because I'm making copies of giant structures
to a managed type and then back, and so that has a really big performance relation with that. There's also when to use an array versus a list, that sort of thing when using FSharp, and that had actually performance impacts when using large data sets, where we actually really needed to use an array instead of a list.
But that's one point that I hit.
Yeah, yeah. At some point, I actually did have something like that.
When I was testing performance of value types and reference types, I actually had a vector3 at one point being a reference type. And so when doing things that have the same memory, it becomes broken when you use a reference type. And so that's why I'm doing the mappings manually right there, because if I want to flip a switch and use a reference type,
I can without breaking any existing code. So that answers. But in the end of everything, I probably would want to do it in the same memory space instead of doing it manually, because that one thing would be a lot easier to do
and probably be more efficient by a little bit, probably not by a whole lot, if that answers your question.
So I have to go to the store. I have to buy a six-pack. No, no, I don't have to do that. The motivation to do stuff like that is really hard. It's more like I'm just trying to plow through it
as much as I can to get something working and then hopefully have something to show. That's the motivation that I have at that point. Some days I had one problem in particular that was completely driving me nuts. I had to actually walk away from it for a couple of days
and then come back to it, and then go, oh, I didn't need to do all that boilerplate work. I could have just done this or that. So a lot of boilerplate could probably just be ripped out with something simple, because I get stuck and actually create worse code. That's not very motivating.
Motivation, I guess, is a really good question. I really like doing it, and I really want to see does F Sharp or functional programming actually do benefit real giant things like this
instead of Fibonacci sequence or something like that, but that's why. Yes. I guess I would say so. That's the first time I actually presented it out there.
That's when Brian was like, where did this come from? Okay.
Well, thank you.