Delta Engine Blog

AI, Robotics, multiplatform game development and Strict programming language

Cute little trick to share memory of C# structs (e.g. with Vector3)

C# Union

We recently had the problem that we want to share some structs we wrote with other structs in other assemblies. For example we cannot change XNA Vector3 (well, its not open source) and even an open source Vector3 like the one from OpenTK should not be changed, we just want to use the libraries. However, if you start using an XNA Vector in some class and then decide you want to switch to OpenTK, you kinda have a problem. The data is EXACTLY the same: X, Y and Z, thats it! Just 3 floats, but many different structs that describe them and have different methods for manipulating them. If you are only using XNA or only OpenTK, then there is no problem, except maybe when you want to extend the given Vector3 struct from the framework/library you are using. Then you might introduce a Vector3Helper class like I did in almost all my XNA projects, which is kinda ugly too.


Our engine however is going to support many frameworks and libraries, and not only that, it is easily extensible and totally customizable. We do not enforce to use XNA or OpenTK or whatever else, it is all a choice of the programmer, whatever he needs. To accomplish this I today present a very little cute trick on how to merge all those structs together and make them easily extensible: Yes, you can add methods to exsting structs without any helper classes!


The magic word is: Union. Now you might say: Wait a second, C++ has unions, but C# does not have them and using pointers is also no fun in C#. Well, you still can use the StructLayout(LayoutKind.Explicit) attribute and then the FieldOffset(0) attribute to mark overlapping memory regions. Just add all the structs (since they are value types and have the sequential x, y, z data there are all the same) and then define x, y and z yourself and use whatever vector you want. Our implementation works a little different because the use of XNA or OpenTK is optional (Tip: Did you know that you can have partial structs? They are fun ^^), but the overall idea is very much the same. I present you the Vector3 struct with XNA and OpenTK support all in one:

 


	/// 
	/// Class with own Vector3 functionality, but still using the same shared
	/// memory as Vector3 does. This not only means we get all the XNA Vector3
	/// (and any other Vector3 we plug in here) functionality for free, but
	/// we can also mix the data types as much as we want (e.g. just set
	/// the xnaVector and all the other vectors are set automatically for us).
	/// 
	[StructLayout(LayoutKind.Explicit)]
	public struct Vector3
	{
		#region Shared vectors (XNA, OpenTK, and our own data)
		// First setup the shared vector structs. Start with an XNA Vector.
		// Note: This could also be private if you want to hide this from the
		// Vector3 users!
		[FieldOffset(0)]
		public Microsoft.Xna.Framework.Vector3 xnaVector;
		// Same with OpenTK.Vector3
		[FieldOffset(0)]
		public OpenTK.Vector3 openTkVector;

		// And then the same data again just as we want to use it! Start with x.
		[FieldOffset(0)]
		public float x;
		// Da Y coordinate
		[FieldOffset(4)]
		public float y;
		// And finally the z coordinate!
		[FieldOffset(8)]
		public float z;

		//Note: This won't work, it would make x, y AND z all start at offset 0!
		//[FieldOffset(0)]
		//public float x, y, z;
		#endregion

		// Go ahead with our constructors, methods, etc.
		// Note: We can easily use the XNA or OpenTK functionality here.
	} // struct Vector3

 

And finally some testing code to prove this actually works (this is just some copied code from our early unit tests, I just added some Console.WriteLine code lines):

 


			// Create a simple vector that happend to have the length 5 (3*3+4*4=25)
			Vector3 vector = new Vector3 { x = 0, y = 3, z = 4 };
			Console.WriteLine("vector=" + vector);
			Console.WriteLine("vector.x=" + vector.x);
			Console.WriteLine("vector.y=" + vector.y);
			Console.WriteLine("vector.z=" + vector.z);

			// Sadly xnaVector and vector are not the same by default :(
			// We could easily add operators to support comparing these too,
			// but for now we should try to use Vector3 exclusively.
			//does not compile: Assert.Equal(vector, vector.xnaVector);
			Console.WriteLine("XNA vector=" + vector.xnaVector);
			Console.WriteLine("OpenTK vector=" + vector.xnaVector);

			// Lets play around with xnaVector and openTkVector
			// First use an XNA method
			//Assert.Equal(5, vector.xnaVector.Length());
			Console.WriteLine("XNA vector length=" + vector.xnaVector.Length());
			// Next try an OpenTK method
			//Assert.Equal(5, vector.openTkVector.Length);
			Console.WriteLine("OpenTK vector length=" + vector.openTkVector.Length);

			// Do a more complicated test, add another XNA vector and then
			// check if the OpenTK vector is also updated.
			Vector3 anotherVector = new Vector3 { x = 0, y = 3, z = -4 };
			// Note: We don't have our own + operator yet, just use the one from XNA
			vector.xnaVector += anotherVector.xnaVector;
			// Finally check if our new vector is 0, 6, 0 now!
			Console.WriteLine("XNA vector+anotherVector=" + vector.xnaVector);
			Console.WriteLine("OpenTK vector+another length=" +
				vector.openTkVector.Length);