Welcome, Guest. Please login or register.

Login with username, password and session length

 
Advanced search

1411425 Posts in 69363 Topics- by 58416 Members - Latest Member: JamesAGreen

April 19, 2024, 08:33:38 AM

Need hosting? Check out Digital Ocean
(more details in this thread)
TIGSource ForumsCommunityTownhallForum IssuesArchived subforums (read only)TutorialsContext-Safe Vector structs in C#
Pages: [1]
Print
Author Topic: Context-Safe Vector structs in C#  (Read 1666 times)
Layl
Level 3
***

professional jerkface


View Profile WWW
« on: October 18, 2014, 05:59:13 PM »

Before reading this tutorial:
I just ran into having to solve this issue myself so I decided to write a quick tutorial about it now that I've solved it. I'm by no means an expert on T4 or code generation in general so please correct me in places where I'm wrong or do things in incorrect ways. Also be aware that code generation generally isn't the answer to your issues, this is just one bit where it seemed to be like the perfect solution.

With that said, let's start!

So you're writing your game, and you end up having to represent in-game or on-screen positions in some way. The first thing you'll probably go to is whatever types the libraries or frameworks you use provide. However, after a while you might notice certain bugs keep creeping in. Little stuff like, you're adding position to a direction. Or you're adding a world distance to a screen position. Things that don't quite make sense conceptually but that your code happily accepts since it's all Vector3s.

You could say your code doesn't check what context something is being used in. It's not context-safe. We can solve this! Let's create a bunch of types specifically for our different contexts. Don't start implementing them yet, we'll get to that later.

Code:
public struct WorldPosition { /* Only allows distance addition. */ }
public struct WorldDistance { /* Only allows distance addition. */ }

public struct ScreenPosition { /* Doesn't allow interaction with any other vector. */ }

Now that we've created these classes you can't do the previously mentioned mistakes anymore! However, if you've skipped my line on not implementing them yet, you'll notice that implementing them would be a serious violation of the DRY principle. You would have to repeat large chunks of code for every single class since they're all so similar. You could make them classes instead and use inheritance, but there's a bunch of problems with that. You would loose the speed C#'s structs give you. You would also still have a lot of code repeat since every operator is the same, just for different types.

So how do we solve this? Can we even solve this since the language doesn't support this kind of "typedef"s? Yes, we can! Let's dive into T4 templates!

Essentially, T4 templates allow you to write code that generates other code. They're built into Visual Studio (and I think also MonoDevelop) and allow easy code generation from inside your editor. Think before using them. They're a last resort and generally not the best solution. In Visual Studio, the Tangible T4 Editor plugin gives a lot of extra features you can use when working with these files, though you might want to adjust the colors it uses for code highlighting a bit for the dark theme.

We probably want to generate our structs in multiple places in our project, so let's organize our files like this:
Code:
/MyProject/Vectors.ttinclude <- An includeable T4 file, here we'll put our method for generating a class.
/MyProject/WorldVectors.tt <- A normal T4 file. This will output a .cs file with our World vectors.
/MyProject/ScreenVectors.tt <- This will output out Screen vectors.

First of all, let's look at our Vectors.ttinclude file. I advice that you paste this into an editor that provides code highlighting for T4 templates, because they can be hard to read without it.

Code:
<#@ IntelliSenseLanguage processor="tangibleT4Editor" language="C#" #>
<#+
void Vector2Struct(string name, string type, string[] allowedAdditions)
{#>
public struct <#= name #>
{
private <#= type #> _x;
private <#= type #> _y;

public <#= name #>(<#= type #> x, <#= type #> y)
{
_x = x;
_y = y;
}

public <#= type #> X { get { return _x; } }
public <#= type #> Y { get { return _y; } }

<#+
foreach (var addition in allowedAdditions)
{
#>
public static <#= name #> operator +(<#= name #> left, <#= addition #> right)
{
return new <#= name #>(left.X + right.X, left.Y + right.Y);
}
<#+
}
#>
}
<#+
}
#>

T4 files are really just C#-style code interweaved with your output code. If you're used to templating languages like Razor, it's not much different. All this file does is create a new function for us called Vector2Struct, which will create a new struct in our output file. Of course alone this won't do much, so let's next look at WorldVectors.tt!

Code:
<#@ template debug="true" hostSpecific="true" #>
<#@ output extension=".cs" #>
<#@ import namespace="System" #>
<#@ include file="Vectors.ttinclude" #>
namespace MyProject
{
<#
Vector2Struct("WorldPosition", "float", new[]
{
"WorldDistance"
});
Vector2Struct("WorldDistance", "float", new[]
{
"WorldDistance"
});
#>
}

After saving this, you should have a new .cs file that includes the two classes this T4 file creates. You can now go ahead and use that function to create whatever class you might want and limit how you can add them to each other.
Logged
Pages: [1]
Print
Jump to:  

Theme orange-lt created by panic