Let us create a small gameplay framework that can update GameObjects. Sometimes you will see that GameObjects are stored in a vector using some kind of pointers.
There are a few problems with this code, one problem would be that only the pointers are stored contiguously in memory. This could lead to a memory fragmentation problem which could be solved by using a custom allocator. Another problem is that only the pointers are prefetched and not the objects themselves which might result in a performance penalty.
We are going to address this problem in D with metaprogramming. Every GameObject will get it's own container.
You could do this by hand, but it would be very tedious and error prone. This could be easily implemented in C++ but from now on we will make use of D's metaprogramming features. Don't worry if you don't understand every thing, I will go over everything in detail.
This is the heart of our Engine class and it makes use of type-level metaprogramming. Everything in the parentheses are templates while Containeris a symbol and GameObjects... is a variadic template.
Container will be our type function which will transform a type into a container of that type. For example it will transform from T to Container!T. Note the ! in D is a template invocation. In C++ you would use <>.
map is usually well know to functional programmers, it takes a list and a function and applies that function to every element of that list. staticMap is a map for types and type values.
If we call staticMap with AliasSeq!(Monster, Player, Weapon) as our list of types and Container as the type level function. The resulting type would be AliasSeq!(Container!(Monster), Container!(Player), Container!(Weapon))
D has a thin wrapper for variadic sequences called AliasSeq. If you are coming from C++ this would roughly be
We now have a small problem, we can not access gameObjectsContainer directly and we need to create a small helper function.
IndexOf is also a metafunction that will take a type and a type sequence and will return the index to the first occurrence of the type in the type sequence. After we have obtained the index we are able to access the container that we want with gameObjectsContainer[indexOfT]
We instantiate a GameObject and pass it to the spawn function. The spawn function knows the type of the GameObject which allows us to access the right container with getContainer. After that we just insert the GameObject at the end of the container. We then wrap the index in a Handle!T, which prevents accidental access of a wrong container.
The first forloop loops over all containers at compile time, the second for loop will update every GameObject at run time.
We are now storing every GameObject in its own container which gives us several benefits. All GameObjects are stored contiguously in memory without any indirection and we have now a lot of type information. It's is now very simple to loop over all Monsters, or maybe you want to apply damage to nearby players.
Usually when I learn a new language I also try to come up with some strange ideas to see how the language will behave with not so common problems.
Goal: Create a function that takes a variable length of arguments and creates the sum of all integers and the sum of all floats.
isIntegral will take a value and check with typeof if it is an integer. Filter is a metafuction that will filter our variadic sequence Ts.... Combining Filter with isIntegral will filter the sequence Ts... so that it will only contain integer values. only() transforms the sequence to a range which allows us to reuse functions from std.algorithm like reduce.
I think it looks quite elegant but sadly only() will result in a copy. Let us see if we can improve on that.
Before we start, let us generalize our isIntegral and isFloatingPoint "functions".
I knew before hand that I could do it like this but in my opinion this looks much worse compared to version 1 and 2. I really wanted to reuse staticFold in my run time version but it didn't seem possible and I almost quit.
Luckily I went back to the drawing board and redefined what my goal was.
Goal: Generate an efficient function at compile time that can be called at run/compile time.
With a much better defined goal I was able to create a new version of staticFold.
I have experimented a lot metaprogramming in various languages. For example macros, AST manipulation, two phase compilation with substitution and I think that template metaprogramming in D is rather elegant.