Quantcast
Channel: GameDev.net
Viewing all 17825 articles
Browse latest View live

C++ Plugin Debug Log with Unity

$
0
0
When I was writing a C++ plugin for Unity in our upcoming iOS game (Shadow Blade), I was thinking that it would be very good if I could write some debug logs into the Unity console. After going on and progressing the plugin without logs, suddenly an idea came to my mind! This is an explanation of how to redirect some debug text from a C++ plugin to Unity's console.

The method is based on Delegates in C#, Delegates can be treated as a typedef of a function pointers in C/C++ and you can call them from unmanaged code.
You can write a function in C# that takes a string as it's argument and print it into the Unity's console window, and then pass a pointer of this function to C++ dll, so you can call the function from C++ by it's pointer, this will redirect your strings from C++ to log window.

First of all define a delegate like this:

using System.Runtime.InteropServices;
...
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate void MyDelegate(string str);
...

This is a delegate of type a function that has a string argument and returns nothing. Take care of your calling conventions, by default calling conventions of a C++ project in Visual Studio are __cdecl (/Gd) you can find it in the project properties -> C/C++ -> Advanced options.

Then write a method that you will call it from C++ to print the log string, this is a member function of a Unity script file (a mono script):

static void CallBackFunction(string str)
{
    Debug.Log("::CallBaaaaaaack : " + str);
}

In the Start() function of your Unity script instantiate the defined delegate:

MyDelegate callback_delegate = new MyDelegate( CallBackFunction );
 
// Convert callback_delegate into a function pointer that can be
// used in unmanaged code.
IntPtr intptr_delegate =
    Marshal.GetFunctionPointerForDelegate(callback_delegate);
  
// Call the API passing along the function pointer.
SetDebugFunction( intptr_delegate );

The SetDebugFunction is a method that assigns the function pointer to a pointer that you defined in your C++ code:

typedef void (*FuncPtr)( const char * );
 
FuncPtr Debug;

You can access this pointer in other source code by an extern modifier or any global access method you know such as writing a singleton class.

extern "C"
{
    EXPORT_API void SetDebugFunction( FuncPtr fp )
    {
        Debug = fp;
    }
}

Don't forget to import it in your C# code:

[DllImport ("UnityPlugin")]
public static extern void SetDebugFunction( IntPtr fp );

Now you can call this function everywhere in your C++ plugin:

...
Debug( "String From C++ Dll" );
...

And the resault:

(I called a function from my plugin that logs a string into console window)
Unity.png

How to Get Started with HTML5

$
0
0
The following article will be a very minimalist and straightforward guide to getting started with HTML5. While it is targeted at beginners, some coding experience is preferable since I won't bother with explaining Javascript basics. I'll leave that to other online tutorials or a Intro to Compsci course. This also is not platform specific in any way. It should run equally well on Linux, Windows or MacOS.

The HTML


Since this will be a web game, an HTML file is required. This is not a proper web page; it's just enough to make it work.


<!DOCTYPE html>

<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="game.js"></script>

<body bgcolor=#808080>

<canvas id="gameCanvas" width="800" height="600"
	onContextMenu="javascript: return false;"></canvas>

</body>


The first line specifies which HTML version to use, specifically 5.

Note:  Some older versions of Internet Explorer force compatability mode on certain web domains which is impossible to override from the web page. This may have been fixed since I last used Internet Explorer. It isn't an issue when testing, but if you try to put your code into a blogspot for example, HTML5 features like canvas may not work for that reason.


The second line is a dependency. JQuery does a lot of very useful things, but I will only be using it for mouse input in this article. The third line is a link to the script containing game logic.

The background color is not necessary but I find it useful.

Finally, you have the HTML5 canvas. The only unusual thing here is the onContextMenu; it returns false when the user right clicks on the canvas and as a result, no menu pops up.

The Game Logic


Declarations


Now to the game logic that goes in game.js. First come the declarations:

var canvasID, context;
var cursorX = 0, cursorY = 0;
var canvasX = 0, canvasY = 0, canvasW = 800, canvasH = 600;
var playerScore=0, aiScore=0;

canvasID is the canvas itself. The context is an object specific to the canvas that contains all the drawing functionality.

The cursor location is self explanatory. The canvas position is necessary in order to calculate the cursor position relative to the canvas. Canvas dimensions as variables also allows for greater flexibility while coding.

Mouse Code and Initialization


These two things need to be done first, which is the only reason why they are lumped together:

function OnMouseMove(e){
	cursorX = e.pageX-canvasX;
	cursorY = e.pageY-canvasY;
}

$(document).ready(function(){
	canvasID = document.getElementById("gameCanvas");
	canvasID.addEventListener("mousemove", OnMouseMove, false);
	context = canvasID.getContext('2d');
	var rect = canvasID.getBoundingClientRect();
	canvasX = rect.left; canvasY = rect.top;
	canvasW = rect.right-canvasX; canvasH = rect.bottom-canvasY;
})

The OnMouseMove function takes an Event Handler as an argument. The event handler has the cursor position relative to the upper left corner of the page, not of the canvas. This is where canvasX and canvasY come in.

The next bit is JQuery magic. When the document is ready (everything is loaded) the function is executed.

Fetching the canvasID is straightforward.

The next line is the last of the mouse input code. It adds an event listener that calls OnMouseMove when the mouse is moved. There are also mousedown and mouseup for mouse button presses and releases. e.which in the event handler specifices which button was pressed.

Context fetching is simple.

Next is where we find the actual location and dimensions of the canvas. getBoundingClientRect() returns the space which is taken up by the client. Getting the location is trivial. The rect right and bottom are the sum of the y position & height and the x position & width.

The Update Loop


function Run(){
	context.fillStyle="black";
	
	context.clearRect(0,0, canvasW, canvasH);
	context.fillRect(0,0, canvasW, canvasH);
}

setInterval(Run,20);

Run will be the update loop. This is where everything that needs to be executed repeatedly will be put. setInterval tells the browser that it needs to be executed every 20 ms (or whatever you specify).

Now to the drawing code. fillStyle and strokeStyle specify the color which you will be working with. The former is for filling shapes, and the latter is for drawing lines.

clearRect empties the canvas of data. This doesn't really matter when you're starting out but it becomes important when you deal with transparency and compositing. The arguments are x, y, width and height. fillRect should be self explanatory; it takes the same arguments.

Drawing the Score


function DrawScores(){
	context.fillStyle = "white";
	context.font = "16px sans-serif";
	context.fillText(aiScore, 30,30);
	context.fillText(playerScore, canvasW-50,30);
}

We have already worked with fillStyle.

The font is specified as a string containing size and typeface. A couple of other valid typefaces are serif and monotype.

fillText takes a string, x coordinate and y coordinate as arguments. strokeText will draw an outline around the text; just be sure to specify strokeStyle instead.

Finally, call it in the Run function after you clear the canvas.

function Run(){
	context.fillStyle="black";
	
	context.clearRect(0,0, canvasW, canvasH);
	context.fillRect(0,0, canvasW, canvasH);
    
	DrawScores();
}

The Classes


Javascript doesn't have classes like most other programming languages do, but they work. The only two things really necessary for a ping-pong game are the ball and the paddles.

The Ball

function Ball(){
	this.radius = 8;
	this.x = canvasW/2;
	this.y = canvasH/2;
	this.vx = 5;
	this.vy = 0;
	
	this.Draw = function(){
		//...\\
	}
	
	this.Update = function(){
        //...\\
	}
}

The above code includes everything the ball will need; radius, position, and velocity. It will also need a Draw function and Update function. The Draw function is the simplest:

this.Draw = function(){
    context.fillStyle = "white";
    
    context.beginPath();
    context.arc(this.x, this.y, this.radius, 0, 3.1416*2);
    context.closePath();
    
    context.fill();
}

The only slightly unusual thing about drawing circles in HTML5 is the fact that the context doesn't have a function for explicitly drawing them. Instead, one can create an arc path. First call the beginPath function, then create an arc of angle 2π, and finally close the path. The arguments are x position, y position, radius, start angle and end angle. More complicated paths are possible, but that is beyond the scope of this article.

Now to the next function:

	this.Update = function(){
    this.x += this.vx;
    this.y += this.vy;
    //move the ball
    
    if(this.x > playerPaddle.x - playerPaddle.w/2 - this.radius){//check if the ball has traveled 
        	//far enough to the right to possibly interact with the right paddle.
        if(this.y >= playerPaddle.y - playerPaddle.h/2 && this.y <= playerPaddle.y + playerPaddle.h/2){
            	//check if it actually hit the paddle
            this.vy = (playerPaddle.y-this.y)*-0.4; //change the y velocity depending on which part 
            	//of the paddle the ball hit.
            this.x = playerPaddle.x - playerPaddle.w/2 - this.radius; //moves the ball out of the paddle
            this.vx*=-1; //make the ball bounce off
        }else{//if the player misses the ball, incriment the ai score and reset the ball.
            aiScore++;
            this.vy=0;
            this.x = canvasW/2;
            this.y = canvasH/2;
        }
        aiPaddle.AIChangeOffset();//AI thing, will make sense later.
    }
    if(this.x < aiPaddle.x + aiPaddle.w/2 + this.radius){//same thing
        if(this.y >= aiPaddle.y - aiPaddle.h/2 && this.y <= aiPaddle.y + aiPaddle.h/2){
            this.vy = (aiPaddle.y-this.y)*-0.2;
            this.x = aiPaddle.x + aiPaddle.w/2 + this.radius;
            this.vx*=-1;
        }else{
            playerScore++;
            this.vy=0;
            this.x = canvasW/2;
            this.y = canvasH/2;
        }
    }
    
    if(this.y<this.radius){
        this.vy*=-1; this.y=this.radius;}
    else if(this.y>canvasH-this.radius){
        this.vy*=-1; this.y=canvasH-this.radius;}
    //have the ball bounce off the top and bottom of the screen.
}

There's nothing HTML5-specific here, just ordinary arithmetic and algebra. I didn't comment the aiPaddle code because it's pretty much the same thing.

The Paddle

Start with the simplest things; variable declarations and the Draw function:

function Paddle(){
	this.x=0;
	this.y = canvasH/2;
	this.w = 32;
	this.h=64;
	
	this.aiv=9; //the maximum velocity with which the ai paddle can move
	this.aiOffset = 0; //how far off center the paddle will be when the ball reaches the end
	
	this.Draw = function(){
		context.fillStyle="white";
		context.fillRect(this.x - this.w/2, this.y - this.h/2, this.w, this.h);
	}

This includes all the relevant physical properties, and a pair of AI-related properties.

	this.FloorAndCeiling = function(){
		if(this.y < this.h/2) this.y = this.h/2;
		if(this.y > canvasH-this.h/2) this.y = canvasH-this.h/2;
	}

The above makes sure that the paddle is not outside the boundaries of the screen. It puts a floor and a ceiling on the y position. The syntax may seem unusual but it works just the same.

	this.PlayerUpdate = function(){
		this.y = cursorY;
		this.FloorAndCeiling();
	}

The above sets the paddle's y position equal to the cursor's y position.

	this.AIUpdate = function(){
		if(ball.vx < 0){//0.1
			if(ball.y + this.h*this.aiOffset > this.y -this.aiv){//1.0
				if(this.y+this.aiv<ball.y + this.h*this.aiOffset)//1.1
					this.y+=this.aiv;
				else
					this.y=ball.y + this.h*this.aiOffset;//1.2
			}
			if(ball.y + this.h*this.aiOffset < this.y + this.aiv){//2.0
				if(this.y-this.aiv>ball.y + this.h*this.aiOffset)
					this.y-=this.aiv;
				else
					this.y=ball.y + this.h*this.aiOffset;
			}
			
			this.FloorAndCeiling(); //3.0
		}
	}

The AI code is a little more difficult to understand conceptually, so the lines are labeled.

0.1 tells the AI to only do stuff if the ball is moving towards it. This isn't absolutely necessary and the game will work fine without this line.

1.0 checks where the ball is relative to the paddle. It's true specifically when the ball is above the paddle. + this.h*this.aiOffset simulates error by changing where the center of the paddle actually is. aiOffset is a number between -0.45 and 0.45 so the new "center" will always be on the paddle.

1.1 checks if the y distance between the paddle "center" and the ball is greater than the maximum velocity at which the AI can move the paddle. If it is, it moves the paddle by that amount. Otherwise,

1.2 the ball is "within range" so instead of overshooting, it just moves to the ball's y position.

2.0 is pretty much the same thing just in the other direction.

3.0 makes sure that the AI doesn't actually move the paddle off screen.

	this.AIChangeOffset = function(){
		this.aiOffset = (Math.random()-0.5)*0.9;
	}
}

Finally, just declare that last function which changes the offset. If you remember, it is called every time the ball bounces off the player's paddle. Remember to close all curly braces!

Variable Initialization


var ball = new Ball();
var aiPaddle = new Paddle(), playerPaddle = new Paddle();
playerPaddle.x = canvasW; //move the paddle to the other side of the screen

Here we create the ball and paddles.

The Game Loop


Finally, update the Run function;

function Run(){
    //NEW CODE PT 1
	aiPaddle.AIUpdate();  
	playerPaddle.PlayerUpdate();
	ball.Update(); //Update all the game objects
	//END NEW CODE PT 1
    
	context.fillStyle="black";
	
	context.clearRect(0,0, canvasW, canvasH);
	context.fillRect(0,0, canvasW, canvasH);//clear the context
	
    //NEW CODE PT 2
	ball.Draw();
	aiPaddle.Draw();
	playerPaddle.Draw();
	DrawScores();//Draw it all
    //END NEW CODE PT 2
}

Everything here is quite straightforward. Just call all the Update and Draw functions in the objects, and draw the score. The only important thing here is to Update before Drawing, and to clear the context before Drawing as well.

Conclusion


This is perhaps the simplest functioning game that one can make. It has a little bit of everything: graphics, input, physics, and AI. It doesn't use any advanced features, but it's a good starting point.

Design Considerations for Custom Binary Formats and Data Compression (Part 1)

$
0
0
While often there are general purpose file-formats or data representations to achieve a goal, sometimes it may make sense to create something a little more specialized, in cases where encoded size may be significant.

I will attempt to address some of the relative tradeoffs based on my own experiences, and experiences with various file-formats.

I will not address the matter of ASCII Text vs Binary file-formats here, and will assume the case where the data in question could not be viably represented in an ASCII format.

Similarly, I will focus more here on formats that can be processed "reasonably efficiently", rather than those focusing on maximizing compression. The primary use cases here are assumed to be for things like network communication and storage of game assets.

Note: I am not presently certain whether this is a "good" topic, or if it is presented adequately. Maybe it should be split up into multiple parts? (As-is, it is a little long).


Byte-Oriented Formats


These are generally much simpler than bit-oriented formats.

In the vast majority of cases, a byte-oriented format is likely to be sufficient for most general data storage needs, with bit-oriented formats more left for cases where either the data is overly bulky, or where there is significant need to minimize storage.

Generally, handling of byte-oriented data tends to be faster than that of handling bit-oriented data.

Within the confines of bytes there are still a few things that can be done to reduce storage requirements:
  • Variable-Length Numbers;
  • Encoding more items in less bytes.

Additionally, these are more fundamental in the sense that most bit-oriented formats will still usually require some sort of byte-oriented packaging format (such as using a TLV format to hold things like headers and similar).

Additional issues to consider:
  • Flexibly representing data structures and allowing for future expansion;
  • Allowing for seeking or resynchronization in stream oriented formats;
  • Allowing for things like random access to (potentially compressed) data.

Variable Length Numbers


One simple trick for saving bytes is based on a pattern:
Small numbers tend to be more common than big numbers.

So, rather than always using 2, 4, or 8 bytes for a number, it may instead be practical to use less bits when it is practical to do so.

A fairly common scheme is to have small values represented as a single byte, with the high-order bit(s) used to indicate when the value is larger than a single byte.

For Example:
0-127 -> 0xxxxxx
128-16383 -> 10xxxxxx xxxxxxxx
16384-2M -> 110xxxxx xxxxxxxx xxxxxxxx
...

Another variant is using the high bit in each byte to indicate that the following byte contains more bits:
0-127 -> 0xxxxxxx
128-16383 -> 1xxxxxxx 0xxxxxxx
16384-2M -> 1xxxxxxx 1xxxxxxx 0xxxxxxx
...

With it then being specified whether the bytes come in high-low or low-high order.

The advantage of the former scheme is that the size of the value can be seen directly from the first byte, which can be used for a more efficient decoder, though with the tradeoff that the encoding/decoding logic tends to be more complex.

The advantage of the second is that the decoding logic is fairly simple, for example:

i=ReadByte(); val=0;
while(i&0x80)
{
    val=(val<<7)|(i&0x7F);
    i=ReadByte();
}
val=(val<<7)|i;

Usually, the encoding logic is a little more involved though, but the level of complexity is typically "fairly similar" with either strategy (usually an if/else tree or a match/encode loop).

Another issue is how to best handle encoding signed numbers.

Typically, a few common options here are:
Sign-extending the most significant bit of the number;
Folding the sign into the least significant bit.

Sign extension has an advantage in that it allows for small positive signed and unsigned numbers to look equivalent, but requires the sign issue to be dealt with directly when encoding and decoding the numbers. This may in turn lead to a bit more logic in the case where both signed and unsigned variations are needed (since the logic for encoding and decoding the numbers may need to be repeated for each variation), or less efficient encoding if we simply encode all unsigned numbers as if they were signed (you may only be able to encode 0-63 in a single byte, regardless of whether the value represents a signed or unsigned quantity).

Sign folding has the advantage that it allows reusing the same number encoding/decoding logic for both signed and unsigned numbers by mapping all signed integers to unsigned equivalents. For example, we may map things such that the unsigned quantities map to signed quantities as:
0, -1, 1, -2, 2, -3, 3, ...

This may be done fairly straightforwardly:

	//to encode:
	uval=(sval>=0)?(sval<<1):(((-sval)<<1)-1);
	//to decode:
	sval=(uval&1)?(-((uval+1)>>1)):(uval>>1);

	//or, expressed alternatively:
	if(sval>=0) { uval=sval<<1; } else { uval=((-sval)<<1)-1; }
	if(uval&1) { sval=-((uval+1)>>1); } else { sval=uval>>1; }

The downside is, granted, that signed and unsigned quantities naturally have different representations.

A few downsides of variable length numbers is that typically they are slightly more expensive to encode and decode, as well as their value generally needs to be known before they are encoded, as they are less subject to the strategy of emitting a placeholder and patching the value later, which will typically require leaving a space for the largest possible, or reasonable, value.

Packing more values into less bytes


This is basically using one or more of these techniques:
Using different bits within a byte or number to represent different values;
Assigning special meanings to various value-ranges, and switching to alternate representations based on these value ranges;
Optionally including or omitting things based on flags or value-ranges.

A very simple and common example of this sort of thing is a "flags" field, where different bits are assigned meanings, and may be set or cleared, or potentially small multi-bit fields are encoded within a flags field.

A simple example of using some of these would be encoding a sparse series of typically small integers as bytes:
High 4 bits gives the number of preceding zeroes;
Low 4 bits gives the numeric value.

So, we may encode the series of numbers 1,0,0,0,3,0,0,0,0,0,9,... as 0x01,0x33,0x59,...

However, it may not escape notice that the maximum run-length and value-range is very limited, and we may also want to be able to encode the end of a list.

The end-of-list case is easy: 0x00. By this, we simply do not allow a run of zero-zeroes to encode a zero.

The other case can be handled via reserving part of the value range for trap-encodings, for example: 0-14=value is encoded directly, 15=value is encoded as a variable-length number. Now, suddenly, we can moderately efficiently encode a sparse collection of arbitrary sized numbers, and without wasting space for long runs of zeroes.

Another possible variant is that rather than the high bits giving zeroes, the bits indicate how many times to repeat a given value. This will slightly increase the cost of runs of zero (since they are now an explicit value, and we also lose the nice EOL special case), however, this will make runs of most other values significantly cheaper.

For example: 1,1,1,1,1,1,1,2,2,2,2,3,3,3,3,3,0,0,4,63,63,63,...
Could be encoded as: 0x61,0x32,0x43,0x10,0x04,0x2F,0x3F,...

Tagged Data and Escape Coding


Tagged Data is basically data where some form of tagging-scheme is intermixed with the data. Commonly this is accomplished by designating one or more byte-values as "special", and not allowing them to appear in normal data, or if they are, it is typically via an escape coding.

For example, we could consider 0xFF to be special and not allow it within ordinary data (example: JPEG). Whenever an 0xFF would appear, we may instead replace it with, say, 0xFF followed by 0x00, with 0xFF followed by other values being used to indicate various markers or tags.

Often, these formats rely on the ability to, when in doubt, scan forwards to the next marker.

This escape-coding case comes at a cost though, namely in that it typically results in a slight expansion of the encoded data, and in some performance-sensitive applications, may incur additional costs related to detecting and handling the escape-byte. It may also potentially hinder the use of fixed-size structures or file offsets, or in some cases result in additional use of temporary buffers.

It is sometimes an option to relax this requirement though, such as relying on heuristic measures to identify markers (example: ZIP, BZ2). In these cases, usually the marker will be longer, and may include check-values in order to help reduce the chances of a false positive.

These types of formats may also allow for optional extension markers, where if such a marker is encountered and is not recognized, the marker's data will be skipped over.

TLV Formats


TLV formats are another common strategy, where TLV stands for Tag Length Value. The Tag is a value that indicates what type of data is contained, the Length tells how big it is, and the Value is all the data for the tag. This construction is often referred to as a Lump or Chunk.

TLV formats often have an advantage in that they can be fairly readily extended without breaking existing implementations, and can easily hold a wide range of data. Very often, TLV formats are used for building the packaging for another file-format.

Depending on the format, unknown lumps may either be ignored or may potentially result in the data being rejected, and some formats will indicate whether or not the decoder should reject unknown lumps on a lump-by-lump basis.

A common example of a TLV format is RIFF, which is used to implement formats such as WAV and AVI (among others).
PNG and MKV are examples of formats which make use of TLV for their packaging (MKV being notable in that it uses variable-length encodings for both tags and lengths).

It is also possible to use both escape-coded markers and TLV, such as to allow for things like resynchronization.

For example, RIFF has a structure like:
tag:FOURCC, length:DWORD, value:BYTE[(length+1)&(~1)]
Where, some chunks (namely: 'RIFF' and 'LIST') may be nested, containing another FOURCC, followed by any number of other chunks. Most other chunks will simply contain data.

Another possible TLV format is:
0xFF, 0xEC, length:WORD, tag:FOURCC, value:BYTE[length-6]
Where FF EC is a magic escape-sequence, and the data uses an escape-coding scheme.

We might use an escape-case for the length, say:
0xFF, 0xEC, 0:WORD, tag:FOURCC, length:DWORD, value:BYTE[length-10]

Or, alternately, allow tag to be 0, at which point the lumps will be limited to 64k, but are appended together to form encode a larger payload.

A few other possible variants:
length:WORD, tag:TWOCC, value:BYTE[length-4]
Where chunk headers are a little more compact.

tag:VLI, length:VLI, value:BYTE[length]
Where tag and length are variable-length. This allows very compact headers in some cases, at the cost that they are more difficult to work with.

Random Access to Data


Generally, random access to data is achieved via the use of some sort of index structure, which may give the offset of each item within the file.

For example, in a video container format, we might encode the offset of every frame in the video file (such as AVI), allowing a video player to easily jump to any position in the file as is desired.

Something like a region-file in a voxel-engine might instead have an index table holding the offset of every chunk in the region, allowing those parts of the world to be decoded as they come into view.

In other cases, we may rely on the size of the data to calculate where to look. An example is in an array of fixed-size structures, where one can easily calculate where each member is, making an explicit index unnecessary.

Another example is a CBR format. In this case, we may have a format, such as for an audio or video file, and may calculate the offset within the file based on its time-value. Like with the array, if every frame (or group of frames) is encoded within a fixed-size box, then we can easily calculate where the frame is at based on the desired time-value and a known reference point.

In either case, to allow for such random access, it is important that the data can be decoded without reliance on preceding data. This may come at a (typically minor) cost to the compression efficiency, since data within the preceding chunk can't be used by the chunk in question, and as a result will need to be repeated.

Packages with Named Members


Sometimes, it may also make sense for a format to use names or keys to refer to data members.

Historical examples of these include the Doom WAD and Quake WAD2 formats, which used a flat directory of lumps with 8 or 16 character names.

A related use-case is the Quake PACK format, which was structurally similar to a WAD file, with the primary practical difference of having a much larger name field (56 characters), and storing a file-path (rather than a simple lump name).

Some formats also use a ZIP file to package up its contents, which has the advantage of being a fairly standardized container which supports both named members and Deflate. Examples: ODT, DOCX, and ORA (OpenRaster).

The advantage is that these allow the ability to dynamically refer to members via names or paths, which can be useful if storing data which is accessed by a name or path.

Sometimes it may also make sense to combine the use of named members with a TLV format, where certain lumps or chunks may be named, rather than by using an explicit directory. This may make sense when the number of lumps is small and the data is all closely related.

Data Compression via Deflate


This has been moved mostly to a new draft.

One simple and common option I will mention here is that of using something like Deflate (for example, via the common zlib library) to compress the data. This can significantly reduce the size for many types of data, and the data can be decoded reasonably quickly.

Many file formats (including, for example, PNG) have used this strategy with good success.


Conclusion


Designing a file format can be useful, but like anything, there may be pitfalls involved, like time/effort involved, or ending up with a format that no other software can read or write, ... So, it may make sense to balance these sorts of things against the needs of the project.

Most of these topics can be found in more detail elsewhere, such as on Wikipedia.

For those who want to know in-depth how various file formats work, it might be interesting to find and read the various file-format specifications.

Also note that file-format specifications may often be a source of nifty tips or tricks, or possible design ideas, ...

While this is far from a complete (or even adequate) coverage of the topic, maybe it is enough to hopefully at least be informative.

Article Update Log


27 Aug 2014: Writing Article
28 Aug 2014: Still writing Article
11 Sep 2014: Split article, data compression moved to new draft.

Prototyping with Graphs

$
0
0
Before we start a process of prototyping the location in 3D, usually using tools from a chosen engine, it’s good to illustrate our idea with graphs. Of course, it’s possible to draw only a simple scheme on paper (what I personally practice) but that way we might miss mistakes we would find by making graphs. Why? Because graphs give us a specific pattern with which we have a detailed overview on our project. It is because we include all the events which have to happen during the gameplay. It allows us to check if everything works fine like moving through locations and playing certain events.

Attached Image: unnamed0.png

The image above shows a simple graph made in a program called yED. It is a sample level with an arrangement of locations and events for a simple platform game with collecting items and fighting monsters.

At this moment, I have to be honest and describe what this graph shows us step by step. The game starts in a green circle, which is a START point. Yellow rectangles symbolize rooms or separate locations, arrows – how we can move between them. As you can see, in “Library” player has to decide which way he wants to go: through “Demonic Tower” or “Library2″. If he chooses the first location, he can visit “Library2”, but not the other way. Next to “Demonic Tower2″ we can see two additional fields: orange trapeze, which shows the moment when enemy spawns, and a violet circle – an item to pick up. There is also one other element worth your attention, located at the end of this episode of the game. It is a conditional statement, based on a simple mechanics “if player does A, then do B”. In this case, in location “Bridge2″ the game will check if the player got a book from the “Demonic Tower” location. If they did, an additional enemy spawns and a text is displayed on the screen. If not, nothing happens.

Constructing this kind of graph is useful because we define how many and what type of blocks we use. In a moment I’ll tell you about methods of creating such a concept. But first, I want to show the same graph as above but modified and expanded for a First Person Perspective game.


Attached Image: unnamed1.png


As you might notice, it is very similar to the previous one but contains extra elements. Because of huge possibilities in edition of graphs, it was simple to insert them.

What is new? I added a script for closing a door (“C_door”, marked in blue) when the player moves to a specific location. Also, you can see a “Key” in the white rhombus which informs the player that they need an adequate item to go on. Finally, we spawn the main boss (“Bridge Boss”) at the end who the player has to defeat to end this level. While fighting, a new “Undead_monster” spawns every twenty seconds.

As you can see, such a graph gives us a good overview to estimate things like: time needed to end an episode, amount of resources needed to create these levels or what the player can or can’t do.

Making a graph at the beginning of a project has the advantage of not leaving any unanswered questions, especially if it’s detailed. We can put in some very complicated conditions or extra events. Furthermore, if we want or need to, we can add things like a “play something now” event at a specific time. With one glance we know the entire structure of our location that allows us to modify our project easily or slap ourselves for some reckless ideas.

I hope I was able to show you, dear reader, what prototyping with graphs is and you would try using those techniques on your own. But if you mastered this weapon, share your thoughts, please. Or maybe you use other methods?

Originally posted on Patrick Polewiak's blog

Autodesk Maya LT 2014 Review

$
0
0
You've got to give a lot of respect to indie game developers. Most indie games are labors of love involving long hours with little or no budget. And since they have little or no budget, most indie games are created using whatever freeware tools are available, which can cause its share of headaches such as unsupported releases and endless converting of files between incompatible formats. And yet, despite these headaches and lack of budget, many indie game projects still see the light of day. Now imagine what they could accomplish with professional level tools.

Autodesk is aware of the plight of indie game developers and is making a version of Maya, known as Maya LT, available to address their needs. Maya LT 2014 is a full-featured version of Maya that is specifically tailored with only the features that game developers need at a significantly discounted price point.

Good Ole Maya


Many small game studios and indie projects are formed in the halls of universities and technical colleges. These institutions typically have labs with the latest software and hardware available including a full array of Autodesk tools, but many studios find that when they leave the resources that the colleges provide in order to turn a profit with their own company that they can't afford the state-of-the-art tools that they once had access to and were familiar with.

Game developers that find themselves in this dilemma can now rejoice that the same software that they used in their final school projects is the tool they can now use to grow their own fledging game company. For many upcoming studios, their tool of choice is now within reach. As an added benefit, using a tool that they are already familiar with will allow them to be much more productive without a steep learning curve to master a new, unfamiliar tool.

What's Missing


If you're familiar with the standard release of Maya from school or by playing with the demo version, then your first question is: what have they disabled? First of all, Maya LT is a complete package by itself separate from Maya. It is not Maya with a bunch of features disabled. Everything that you see in the package is there for a reason.

Perhaps the biggest difference between Maya LT and the standard edition is that the rendering features have been removed, but since game developers typically use the game engine to view their scene, the rendering features really aren't missed by game developers.

The other major omission from the standard version is that most of the special effects and dynamic systems have been removed including particles, cloth, hair, fluids, muscles and dynamics fields and constraints. These effects usually deal with complex meshes and high update rates that make them beyond what the game engine can deal with, so again, no loss here for game developers. The dynamic effects of fire, smoke, lightning, shatter and surface flow are also gone. The Paint Effects features are also not included.

Maya LT includes several Deformers including Blend Shapes, Lattice and many of the Nonlinear Deformers like Bend, Flare, Squash, Twist, and Wave, but the more advanced ones like Wrap, Jiggle, Cluster, Sculpt, Wrinkle and Wire are also out.

In addition, all the animation caching and MEL Scripting are not included.

What's Included


The real story is not what is missing, but what is included. Maya LT 2014 includes all the features that game developers need to create the next major gaming sensation including most of the main modeling and character animation features, along with the texturing and standard animation features.

On the modeling front, Maya LT includes the features for modeling with polygons, subdivision surfaces and NURBS. It also has features for sculpting, reducing and cleaning up polygon meshes and mirroring changes. It also includes the new Modeling Toolkit.

With the release of Maya 2014, Maya's modeling tools were updated with a new Modeling Toolkit. This toolkit is a simple panel that fits where the Attribute Editor goes and holds all the tools you need to select and work with mesh components. It also includes several indispensible modeling tools for beveling, bridging, connecting, extruding, welding and cutting. There is also a new Quad Draw tool that makes creating new quads really easy. You can also use this tool to create a set of quads based on the topology of an existing model, as shown in Figure 1.


Attached Image: Figure 1 - MayaLT_QuadDraw_Retopo.jpg
Figure 1: Retopology with the new Quad Draw tool is easy work. Image provided courtesy of Autodesk.


If you prefer to work with NURBS curves and surfaces, most of the features are still there including Boolean and operations to Revolve, Loft, Extrude and Bevel.

On the texturing side, Maya LT also includes the Hypershade and all the wonderful texturing tools found in Maya, as shown in Figure 2. It also includes the 3D Paint tool for painting directly on a mesh in the viewport and the ability to bake textures. There is also a full editing solution for working with UVs including several mapping options.


Attached Image: Figure 2 - MayaLT__HyperShade_DX11_UberShader__1920x1080.jpg
Figure 2: Maya LT has all the material and texturing tools that you'll need. Image provided courtesy of Autodesk.


Maya LT also includes a large assortment of animation tools including the Graph Editor, the Dope Sheet and the Blend Shape interface. The Motion Paths feature along with the standard timeline keyframes are also there. Happily, most of the character animation features are there also including HumanIK. The Skeleton building tools and skin binding features are also there. Maya LT also includes the Character Controls panel for building and defining characters, as shown in Figure 3.


Attached Image: Figure 3 - MayaLT__HumanIK_AnimationGraph_and_Outliner__1920x1080.jpg
Figure 3: Maya LT also has the character animation tools including HumanIK. Image courtesy of Autodesk.


Although Maya LT doesn't include rendering features, the Viewport 2.0 features are often enough to provide you with a detailed representation of the scene, as shown in Figure 4. Using the advance viewport features, you can view lighting effects, shadows, ambient occlusion, depth of field, motion blur, and transparency without having to export the scene. For viewing game assets, the viewport can be switched to DirectX 11 mode making specialized DX11 shader effects visible within the viewport. Maya LT also includes a DX11 Ubershader for creating and visualizing effects such as translucency and blurred reflections.


Attached Image: Figure 4 - MayaLT_Viewport2.jpg
Figure 4: Viewing game assets in the Viewport is often enough to verify the scene. Image provided courtesy of Autodesk.


Because Maya LT is a subset of Maya, many of the great new features that appear year after year with each new release will also get updated in Maya LT and this applies to the 2014 version also. This also insures that the software will be supported and maintained over time, so as game engines develop, Maya will improve right along with them. If you opt for any of the lease options, then you'll have access to the latest features as they become available to subscription users.

Efficient Workflows


Maya LT can seamless fit into most development pipelines. Through the FBX format that is standard in Maya LT, files can be moved directly to several game engines including the Unity 3D Engine and the Unreal Engine without having to convert the files. Maya LT can also import several 3D formats including Maya's own .ma or .mb files as well as OBJ and FBX. It can also import the AI and EPS vector formats and the BMP, PNG, DDS, EXR, TGA and TIFF texture formats. Be aware that scene files created in Maya LT have their own format, mlt. The software has a limitation on exporting more than 25,000 polygons per object.

The Price Point


Maya LT is a great tool with all the features you want as a game developer, but the one big question remaining is the price. The greatest tool in the world doesn't help a project if it is financially out of reach. The price point for Maya LT is the best news of all. Licensing Maya LT 2014 is available as a standalone license or as a month to month lease. The price point is very competitive and well within the budget of indie game projects.

The current prices as of 2014 are set at $795 for a stand-alone license. Maya LT can also be leased for $50 a month, $125 per quarter or $400 per year. These prices are, of course, subject to change.

Summary


If you've been putting off creating that game design that's been kicking around your mind because you didn't have access to professional level tools, well, your barrier to entry has just been lifted. Whether you need a 3D solution to create simple interface elements or advanced character animation, you'll find all the bells and whistles that you need in this robust industry-proven tool.

Maya LT has plenty of killer features, so many if fact, that I'd doubt you'll even miss the features that aren't there. Kudos to the Autodesk team for seeing a need in the industry and for creating a specific tool to meet that need.

Maya LT 2014 is available for Windows and Macintosh OS X. For more information on any of these products, visit the Autodesk web site located at www.autodesk.com. There is also a community site set up for Maya LT users at area.autodesk.com/mayalt. A free trial version is also available there.

Grounded Pointers

$
0
0
Not so long ago one of our colleagues left the team and joined one company developing software for embedded systems. There is nothing extraordinary about it: in every firm people come and go, all the time. Their choice is determined by bonuses offered, the convenience aspect, and personal preferences. What we find interesting is quite another thing. Our ex-colleague is sincerely worried about the quality of the code he deals with in his new job. And that has resulted in us writing a joint article. You see, once you have figured out what static analysis is all about, you just don't feel like settling for "simply programming".

Forest Reserves


I find an interesting phenomenon occurring in the world nowadays. What happens when a software development department turns into a secondary entity not closely related to the company's basic area of activity? A forest reserve appears. However significant and critical the company's area of activity may be (say, medicine or military equipment), a small swamp appears anyway, where new ideas get stuck and 10-year old technologies are in use.

Here you are a couple of extracts from the correspondence of a man working in the software development department at some nuclear power plant:

And then he says, "What for do we need git? Look here, I've got it all written down in my paper notebook."

...

And do you have any version control at all?

2 men use git. The rest of the team use numbered zip's at best. Though it's only 1 person with zip's I'm sure about.

Don't be scared. Software developed at nuclear power plants may serve different purposes, and no one has abolished hardware security yet. In that particular department, people collect and process statistical data. Yet the tendency to swamping is pretty obvious. I don't know why it happens, but the fact is certain. What's interesting, the larger the company, the more intense the swamping effect.

I want to point it out that stagnation in large companies is an international phenomenon. Things are quite the same abroad. There is an article on the subject, but I don't remember its title. I spent quite a time trying to find it, but in vain. If somebody knows it, give me the link please so that I could post it. In that article, a programmer is telling a story about him having worked at some military department. It was - naturally - awfully secret and bureaucratic - so much secret and bureaucratic that it took them several months to agree on which level of access permissions he could be granted to work on his computer. As a result, he was writing a program in Notepad (without compiling it) and was soon fired for inefficiency.

Foresters


Now let's get back to our ex-colleague. Having come to his new office, he was struck with kind of a cultural shock. You see, after spending so much time and effort on studying and working with static analysis tools, it's very painful to watch people ignore even compiler warnings. It's just like a separate world where they program according to their own canons and even use their own terms. The man told me some stories about it, and most of all I liked the phrase "grounded pointers" common among the local programmers. See how close they are to the hardware aspect?

We are proud of having raised inside our team a skilled specialist who cares about the code quality and reliability. He hasn't silently accepted the established situation; he is trying to improve it.

As a start, he did the following. He studied the compiler warnings, then checked the project with Cppcheck and considered preventing typical mistakes in addition to making some fixes.

One of his first steps was preparing a paper aiming at improving the quality of the code created by the team. Introducing and integrating a static code analyzer into the development process might be the next step. It will certainly not be PVS-Studio: first, they work under Linux; second, it's very difficult to sell a software product to such companies. So, he has chosen Cppcheck for now. This tool is very fine for people to get started with the static analysis methodology.

I invite you to read the paper he has prepared. It is titled "The Way You Shouldn't Write Programs". Many of the items may look written pretty much in the Captain Obvious style. However, these are real problems the man tries to address.

The Way You Shouldn't Write Programs


Issue 1


Ignoring compiler warnings. When there are many of them in the list, you risk easily missing genuine errors in the lately written code. That's why you should address them all.

Issue 2


In the conditional statement of the if operator, a variable is assigned a value instead of being tested for this value:

if (numb_numbc[i] = -1) { }

The code is compiled well in this case, but the compiler produces a warning. The correct code is shown below:

if (numb_numbc[i] == -1) { }

Issue 3


The statement using namespace std; written in header files may cause using this namespace in all the files which include this header, which in turn may lead to calling wrong functions or the occurrence of name collisions.

Issue 4


Comparing signed variables to unsigned ones:

unsigned int BufPos;
std::vector<int> ba;
....
if (BufPos * 2 < ba.size() - 1) { }

Keep in mind that mixing signed and unsigned variables may result in:
  • overflows;
  • occurrence of always true or always false conditions and, as a consequence, infinite loops;
  • a value larger than INT_MAX may be written into a signed variable (and it will be negative);
  • an int-variable participating in addition/subtraction/etc. with an unsigned variable becomes unsigned too (so that negative values turn into large positive ones);
  • other unexpected nice things
The foregoing code sample incorrectly handles the situation of the ba array being empty. The expression ba.size() - 1 evaluates to an unsigned size_t value. If the array contains no items, the expression evaluates to 0xFFFFFFFFu.

Issue 5


Neglecting usage of constants may lead to overlooking hard-to-eliminate bugs. For example:

void foo(std::string &str)
{
  if (str = "1234")
  {
  }
}

The = operator is mistakenly used instead of ==. If the str variable were declared as a constant, the compiler would not even compile the code.

Issue 6


Pointers to strings are compared instead of strings themselves:

Ãhar TypeValue [4];
...
if (TypeValue == "S") {}

Even if the string S is stored in the variable TypeValue, the comparison will always return false. The correct way to compare strings is to use the special functions strcmp or strncmp.

Issue 7


Buffer overflow:

memset(prot.ID, 0, sizeof(prot.ID) + 1);

This code may cause several bytes of the memory area right after prot.ID to be cleared as well.

Don't mix up sizeof() and strlen(). The sizeof() operator returns the complete size of an item in bytes. The strlen() function returns the string length in characters (without counting the null terminator).

Issue 8


Buffer underflow:

struct myStruct
{
  float x, y, h;
};
myStruct *ptr;
 ....
memset(ptr, 0, sizeof(ptr));

In this case only N bytes will be cleared instead of the whole *ptr structure (N is the pointer size on the current platform). The correct way is to use the following code:

  myStruct *ptr;
  ....
  memset(ptr, 0, sizeof(*ptr));

Issue 9


Incorrect expression:

if (0 < L < 2 * M_PI) { }

The compiler doesn't see any error here, yet the expression is meaningless, for you will always get either true or false when executing it, the exact result depending on the comparison operators and boundary conditions. The compiler generates a warning for such expressions. The correct version of this code is:

 if (0 < L && L < 2 * M_PI) { }

Issue 10


unsigned int K;
....
if (K < 0) { }
...
if (K == -1) { }

Unsigned variables cannot be less than zero.

Issue 11


Comparing a variable to a value it can never reach. For example:

short s;
...
If (s==0xaaaa) { }

The compiler produces warnings against such things.

Issue 12


Memory is allocated with the help of new or malloc, while forgotten to be freed through delete/free correspondingly. It may look something like this:

void foo()
{
  std::vector<int> *v1 = new std::vector<int>;
  std::vector<int> v2;
  v2->push_back(*v1);
  ...
}

Perhaps it was the pointer to std::vector<int> that used to be saved into v2 before. Now, due to modifications of some code parts, it is no longer needed and there are just int values being saved. At the same time, memory allocated for v1 is not freed, as that was not needed in earlier times. To fix the code we should add the statement delete v1 at the end of the function, or use smart pointers.

Even better is to bring refactoring to an end, making v1 a local object, since you no longer need to pass it anywhere:

void foo()
{
  std::vector<int> v1;
  std::vector<int> v2;
  v2->push_back(v1[0]);
  ...
}

Issue 13


Memory is allocated through new[] and freed through delete. Or, vice versa, memory is allocated through new and freed through delete[].

Issue 14


Using uninitialized variables:

int sum;
...
for (int i = 0; i < 10; i++)
{
  sum++;
}

In C/C++, variables are not initialized to zero by default. Sometimes code only seems to run well, which is not so - it's merely luck.

Issue 15


A function returns a reference or pointer to local objects:

char* CreateName()
{
  char FileName[100];
  ...
  return FileName;
}

On leaving the function, FileName will refer to an already-freed memory area, since all the local objects are created on the stack, so it's impossible to handle it correctly any further.

Issue 16


Values returned by functions are not checked, while they may return an error code or -1 in case of an error. It may happen that a function returns an error code, us continuing to work without noticing and reacting to it in any way, which will result in a sudden program crash at some point. Such defects take much time to debug after that.

Issue 17


Neglecting usage of special static and dynamic analysis tools, as well as creation and usage of unit-tests.

Issue 18


Being too greedy for adding some parentheses in math expressions, which results in the following:

D = ns_vsk.bit.D_PN_ml + (int)(ns_vsk.bit.D_PN_st) << 16;

In this case, addition is executed in the first place and only then left-shift is. See "Operation priorities in C/C++". Judging by the program logic, the order the operations must be executed in is quite reverse: shift first, then addition. A similar error occurs in the following fragment:

#define A 1
#define B 2
#define TYPE A | B
if (type & TYPE) { }

The error here is this: the programmer forgot to enclose the TYPE macro in parentheses. This results in first executing the type & A expression and only then the (type & A ) | B expression. As a consequence, the condition is always true.

Issue 19


Array index out of bounds:

int mas[3];
mas[0] = 1;
mas[1] = 2;
mas[2] = 3;
mas[3] = 4;

The mas[3] = 4; expression addresses a non-existing array item, since it follows from the declaration of the int mas[N] array that its items can be indexed within the range [0...N-1].

Issue 20


Priorities of the logical operations && and || are mixed up. The && operator has a higher priority, so in this condition:

if (A || B && C) { }

B && C will be executed first, while the rest of the expression will be executed after that. This may not conform to the required execution logic. It's often assumed that logical expressions are executed from left to right. The compiler generates warnings for such suspicious fragments.

Issue 21


An assigned value will have no effect outside the function:

void foo(int *a, int b)
{
  If (b == 10)
  {
    *a = 10;
  }
  else
  {
    a = new int;
  }
}

The pointer a cannot be assigned a different address value. To do that, you need to declare the function in the following way:

void foo(int *&a, int b) {....}

or:

void foo(int **a, int b) {....}

References:


  1. "Enough Rope to Shoot Yourself in the Foot. Rules for C and C++ Programming". Allen I. Holub;
  2. "C++ Coding Standards: 101 Rules, Guidelines, and Best Practices". Herb Sutter, Andrei Alexandrescu;
  3. "Code Complete". Steve McConnel;
  4. "C++ Gotchas: Avoiding Common Problems in Coding and Design". Stephen C. Dewhurst;
  5. "Effective C++: 50 Specific Ways to Improve Your Programs and Designs". Scott Meyers.

Conclusion


I haven't drawn any specific and significant conclusions. I'm only sure that in one particular place the situation with software development is beginning to improve. And that's pleasant.

On the other hand, it makes me sad that many people haven't even heard of static analysis. And these people are usually responsible for serious and important affairs. The area of programming is developing very fast. As a result, those who are constantly "working at work" fail to keep track of contemporary tendencies and tools in the industry. They eventually grow to work much less efficiently than freelance programmers and programmers engaged in startups and small companies.

Thus we get a strange situation. A young freelancer can do his work better (because he has knowledge: TDD, continuous integration, static analysis, version control systems, and so on) than a programmer who has worked for 10 years at Russian Railways/nuclear power plant/… (add your variant of some large enterprise). Thank God, it's so far not always like that. But still it happens.

Why am I feeling sad about this? I wish we could sell PVS-Studio to them. But they don't even have a slightest suspicion about existence and usefulness of such tools. :)

Math for Game Developers: Advanced Matrices

$
0
0
Math for Game Developers is exactly what it sounds like - a weekly instructional YouTube series wherein I show you how to use math to make your games. Every Thursday we'll learn how to implement one game design, starting from the underlying mathematical concept and ending with its C++ implementation. The videos will teach you everything you need to know, all you need is a basic understanding of algebra and trigonometry. If you want to follow along with the code sections, it will help to know a bit of programming already, but it's not necessary. You can download the source code that I'm using from GitHub, from the description of each video. If you have questions about the topics covered or requests for future topics, I would love to hear them! Leave a comment, or ask me on my Twitter, @VinoBS

Note:  
The video below contains the playlist for all the videos in this series, which can be accessed via the playlist icon in the bottom-right corner of the embedded video frame once the video is playing. The first video in the series is loaded automatically


Note:  
This is still an ongoing series of videos - check back every Thursday for the next installment!


Advanced Matrices



Manual on Development of Visual Studio 2005-2012 and Atmel Studio Plugins in C#

$
0
0

Abstract


About a year ago we published in our blog a series of articles on development of Visual Studio plugins in C#. We have recently revised those materials and added new sections and now invite you to have a look at the updated version of the manual.

Creating extension packages (plug-ins) for Microsoft Visual Studio IDE appears as quite an easy task at the first sight. There exist an excellent MSDN documentation, as well as various articles, examples and a lot of other additional sources on this topic. But, at the same time, it could also appear as a difficult task when an unexpected behavior is encountered along the way. Although it can be said that such issues are quite common to any programming task, the subject of IDE plug-in development is still not thoroughly covered at this moment.

We develop PVS-Studio static code analyzer. Although the tool itself is intended for C++ developers, quite a large fragment of it is written in C#. When we just had been starting the development of our plug-in, Visual Studio 2005 had been considered as modern state-of-the-art IDE. Although, at this moment of Visual Studio 2012 release, some could say that Visual Studio 2005 is not relevant anymore, we still provide support for this version in our tool. During our time supporting various Visual Studio versions and exploring capabilities of the environment, we've accumulated a large practical experience on how to correctly (and even more so incorrectly!) develop IDE plug-ins. As holding all of this knowledge inside us was becoming unbearable, we've decided to publish it here. Some of our solutions that seem quite obvious right now were discovered in the course of several years. And the same issues could still haunt other plug-in developers.

The following topics will be covered:
  • basic information on creating and debugging MSVS plug-ins and maintaining these extensibility projects for several versions of Visual Studio inside a common source code base;
  • overview of Automation Object Model and various Managed Package Framework (MPF) classes
  • extending interface of the IDE though the automation object model's API (EnvDTE) and MPF (Managed Package Framework) classes with custom menus, toolbars, windows and options pages;
  • overview of Visual Studio project model; Atmel Studio IDE, which is based on Visual Studio Isolated Shell, as an example of interaction with custom third-party project models.
  • utilizing Visual C++ project model for gathering data needed to operate an external preprocessor/compiler, such as compilation arguments and settings for different platforms and configurations;
A list of more detailed in-depth references for the article covered here are available at the end of each topic through the links to MSDN library and several other external resources.

The articles will cover the extension development only for Visual Studio 2005 and later versions. This limitation reflects that PVS-Studio also supports integration to Visual Studio starting only from version 8 (Visual Studio 2005). The main reason behind this is that a new extensibility API model was introduced for Visual Studio 2005, and this new version is not backward-compatible with previous IDE extensibility APIs.


Creating, debugging and deploying extension packages for Microsoft Visual Studio 2005/2008/2010/2012


This item contains the overview of several different methods for extending Visual Studio IDE functionality. The creation, debugging, registration and end-user deployment of Visual Studio extension packages will be explained in detail.

Creating and debugging Visual Studio and Visual Studio Isolated Shell VSPackage extension modules


There exists a number of ways to extend Microsoft Visual Studio features. On the most basic level it's possible to automate simple routine user actions using macros. An add-In plug-in module can be used for obtaining an access to environment's UI objects, such as menu commands, windows etc. Extension of IDE's internal editors is possible through MEF (Managed Extensibility Framework) components (starting with MSVS 2010). Finally, a plug-in of the Extension Package type (known as VSPackage) is best suited for integrating large independent components into Visual Studio. VSPackage allows combining the environment automation through Automation Object Model with usage of Managed Package Framework classes (such as Package). In fact, while Visual Studio itself provides only the basic interface components and services, such standard modules as Visual C++ or Visual C# are themselves implemented as IDE extesnions.

In its earlier versions, PVS-Studio plug-in (versions 1.xx and 2.xx to be precise, when it was still known as Viva64) existed as an Add-In package. Starting with PVS-Studio 3.0 it was redesigned as VSPackage because the functionality Add-in was able to provide became insufficient for the tasks at hand and also the debugging process was quite inconvenient. After all, we wanted to have our own logo on Visual Studio splash screen!

VSPackage also provides the means of extending the automation model itself by registering user-defined custom automation objects within it. Such user automation objects will become available through the same automation model to other user-created extensibility packages, providing these packages with access to your custom components. This, in turn, allows third-party developers to add the support of new programming languages and compilers through such extensions into the IDE and also to provide interfaces for the automation of these new components as well.

Besides extending the Visual Studio environment itself, VSPackage extensions could be utilized for addition of new features into Visual Studio Isolated\Integrated shells. Isolated\integrated shell provides any third-party developer with the ability to re-use basic interface components and services of Visual Studio (such as a code editor, autocompletion system etc.), but also to implement the support of other custom project models and\or compilers. Such a distribution will not include any of Microsoft proprietary language modules (such as Visual C++, Visual Basic and so on), and it could be installed by an end-user even if his or her system does not contain a previous Visual Studio installation.

An isolated shell application will remain a separate entity after the installation even if the system contains a previous Visual Studio installation, but an integrated shell application will be merged into the preinstalled version. In case the developer of isolated\integrated shell extends the Visual Studio automation model by adding interfaces to his or her custom components, all other developers of VSPackage extensions will be able to add such components as well. Atmel Studio, an IDE designed for the development of embedded systems, is an example of Visual Studio Isolated Shell application. Atmel Studio utilizes its own custom project model which, in turn, itself is the implementation of a standard Visual Studio project model for the MSBuild, and the specific version of the gcc compiler.

Projects for VSPackage plug-in modules. Creating the extension package.

Let's examine the creation of Visual Studio Package plug-in (VSPackage extension). Contrary to Add-In plug-ins, developing VS extension packages requires the installation of Microsoft Visual Studio SDK for a targeted version of IDE, i.e. a separate SDK should be installed with every version of Visual Studio for which an extension is being developed. In case of the extension that targets Visual Studio Isolated\Integrated Shell, an SDK for the version of Visual Studio on which such a shell is based will be required.

We will be examining extension development for the 2005, 2008, 2009 and 2012 versions of Visual Studio and Visual Studio 2010 based Isolated Shells. Installation of Visual Studio SDK adds a standard project template for Visual Studio Package (on the 'Other Project Types -> Extensibility' page) to VS template manager. If selected, this template will generate a basic MSBuild project for an extension package, allowing several parameters to be specified beforehand, such as a programming language to be used and the automatic generation of several stub components for generic UI elements, such as menu item, an editor, user tool window etc.

We will be using a C# VSPackage project (csproj), which is a project for managed dynamic-link library (DLL). The corresponding csproj MSBuild project for this managed assembly will also contain several XML nodes specific to a Visual Studio package, such as VSCT compiler and IncludeinVSIX (in later IDE versions).

The main class of an extension package should be inherited from the Microsoft.VisualStudio.Shell.Package. This base class provides managed wrappers for IDE interaction APIs, implementation of which is required from a fully-functional Visual Studio extension package

public sealed class MyPackage: Package
{
  public MyPackage ()
  {}
  ...
}

The Package class allows overriding of its base Initialize method. This method receives execution control at the moment of package initialization in the current session of IDE.

protected override void Initialize()
{
  base.Initialize();

  ...
}

The initialization of the module will occur when it is invoked for the first time, but it also could be triggered automatically, for example after IDE is started or when user enters a predefined environment UI context state.

Being aware of the package's initialization and shutdown timings is crucial. It's quite possible that the developer would be requesting some of Visual Studio functionality at the moment when it is still unavailable to the package. During PVS-Studio development we've encountered several such situations when the environment "punished us" for not understanding this, for instance, we are not allowed to "straightforwardly" display message boxes after Visual Studio enters a shutdown process.

Debugging extension packages. Experimental Instance.

The task of debugging a plug-in module or extension intended for an integrated development environment is not quite a trivial one. Quite often such environment itself is utilized for plug-in's development and debugging. Hooking up an unstable module to this IDE can lead to instability of the environment itself. The necessity to uninstall a module under development from the IDE before every debugging session, which in turn often requires restarting it, is also a major inconvenience (IDE could block the DLL that needs to be replaced by a newer version for debugging).

It should be noted that a VSPackage debugging process in this aspect is substantially easier than that of an Add-In package. This was one of the reasons for changing the project type of PVS-Studio plug-in.

VSPackage solves the aforementioned development and debugging issues by utilizing Visual Studio Experimental Instance mechanism. Such an experimental instance could be easily started by passing a special command line argument:

"C:\Program Files (x86)\Microsoft Visual Studio 10.0\
  Common7\IDE\devenv.exe" /RootSuffix Exp

An experimental instance of the environment utilizes a separate independent Windows registry hive (called experimental hive) for storing all of its settings and component registration data. As such, any modifications in the IDE's settings or changes in its component registration data, which were made inside the experimental hive, will not affect the instance which is employed for the development of the module (that is your main regular instance which is used by default).

Visual Studio SDK provides a special tool for creating or resetting such experimental instances — CreateExpInstance. To create a new experimental hive, it should be executed with these arguments:

CreateExpInstance.exe /Reset /VSInstance=10.0 /RootSuffix=PVSExp

Executing this command will create a new experimental registry hive with a PVSExp suffix in its name for the 10th version of IDE (Visual Studio 2010), also resetting all of its settings to their default values in advance. The registry path for this new instance will look as follows:

HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\10.0PVSExp

While the Exp suffix is utilized by default for package debugging inside VSPackage template project, other experimental hives with unique names could also be created by the developer at will. To start an instance of the environment for the hive we've created earlier (containing PVSExp in its name), these arguments should be used:

"C:\Program Files (x86)\Microsoft Visual Studio 10.0\
  Common7\IDE\devenv.exe" /RootSuffix PVSExp

A capacity for creating several different experimental hives on a single local workstation could be quite useful, as, for example, to provide a simultaneous and isolated development of several extension packages.

After installing the SDK package, a link is created in the Visual Studio program's menu group for resetting the default Experimental Instance for this version of the IDE (for instance, "Reset the Microsoft Visual Studio 2010 Experimental Instance").

In case of extension targeting an Isolated Shell, the issues with the "corruption" of the development environment are irrelevant, and so there is no need for Experimental Instance utilization. But, in any case, the faster you'll figure out how the debugging environment works, the fewer issues you'll encounter in understanding how plug-in initialization works during development.

Registering and deploying Visual Studio extension packages


Registering a VS extension package requires registering a package itself, as well as registering all of the components it integrates into the IDE (for example, menu items, option pages, user windows etc.). The registration is accomplished by creating records corresponding to these components inside the main system registry hive of Visual Studio.

All the information required for registration is placed, after building your VSPackage, inside a special pkgdef file, according to several special attributes of the main class of your package (which itself should be a subclass of the MPF Package class). The pkgdef can also be created manually using the CreatePkgDef. This tool collects all of the required module registration information from these special attributes by the means of .NET reflection. Let's study these registration attributes in detail.

The PackageRegistration attribute tells the registration tool that this class is indeed a Visual Studio extension package. Only if this attribute is discovered will the tool perform its search for additional ones.

[PackageRegistration(UseManagedResourcesOnly = true)]

The Guid attribute specifies a unique package module identifier, which will be used for creating a registry sub-key for this module in Visual Studio hive.

[Guid("a0fcf0f3-577e-4c47-9847-5f152c16c02c")]

The InstalledProductRegistration attribute adds information to 'Visual Studio Help -> About' dialog and the loading splash screen.

[InstalledProductRegistration("#110", "#112", "1.0", 
  IconResourceID = 400)]

The ProvideAutoLoad attribute links automatic module initialization with the activation of a specified environment UI context. When a user enters this context, the package will be automatically loaded and initialized. This is an example of setting module initialization to the opening of a solution file:

[ProvideAutoLoad("D2567162-F94F-4091-8798-A096E61B8B50")]

The GUID values for different IDE UI contexts can be found in the Microsoft.VisualStudio.VSConstants.UICONTEXT class.

The ProvideMenuResource attribute specifies an ID of resource that contains user created menus and commands for their registration inside IDE.

[ProvideMenuResource("Menus.ctmenu", 1)]

The DefaultRegistryRoot attribute specifies a path to be used for writing registration data to the system registry. Starting with Visual Studio 2010 this attribute can be dropped as the corresponding data will be present in manifest file of a VSIX container. An example of registering a package for Visual Studio 2008:

[DefaultRegistryRoot("Software\\Microsoft\\VisualStudio\\9.0")]

Registration of user-created components, such as toolwidows, editors, option pages etc. also requires the inclusion of their corresponding attributes for the user's Package subclass. We will examine these attributes separately when we will be examining corresponding components individually.

It's also possible to write any user-defined registry keys (and values to already existing keys) during package registration through custom user registration attributes. Such attributes can be created by inheriting the RegistrationAttribute abstract class.

[AttributeUsage(AttributeTargets.Class, Inherited = true,
  AllowMultiple = false)]
    public class CustomRegistrationAttribute : RegistrationAttribute
    {
    }

The RegistrationAttribute-derived attribute must override its Register and Unregister methods, which are used to modify registration information in the system registry.

The RegPkg tool can be used for writing registration data to Windows registry. It will add all of the keys from pkgdef file passed to it into the registry hive specified by the /root argument. For instance, the RegPkg is utilized by default in Visual Studio VSPackage project template for registering the module in the Visual Studio experimental hive, providing convenient seamless debugging of the package being developed. After all of the registration information have been added to the registry, Visual Studio (devenv.exe) should be started with '/setup' switch to complete registration for new components inside the IDE.

Deploying plug-ins for developers and end-users. Package Load Key.

Before proceeding to describe the deployment process itself, one particular rule should be stressed:

Each time after a new version of the distribution containing your plug-in is created, this new distribution should be tested on a system without Visual Studio SDK installed, as to make sure that it will be registered correctly on the end-user system.

Today, as the releases of early versions of PVS-Studio are past us, we do not experience these kinds of issues, but several of these early first versions were prone to them.

Deploying a package for Visual Studio 2005/2008 will require launching of regpkg tool for a pkgdef file and passing the path to Visual Studio main registry hive into it. Alternately, all keys from a pkgdef can be written to Windows registry manually. Here is the example of automatically writing all the registration data from a pkgdef file by regpkg tool (in a single line):

RegPkg.exe /root:Software\Microsoft\VisualStudio\9.0Exp
  "/pkgdeffile:obj\Debug\PVS-Studio-vs2008.pkgdef"
  "C:\MyPackage\MyPackage.dll"

After adding the registration information to the system registry, it is necessary to start Visual Studio with a /setup switch to complete the component registration. It is usually the last step in the installation procedure of a new plug-in.

Devenv.exe /setup

Starting the environment with this switch instructs Visual Studio to absorb resource metadata for user-created components from all available extension packages, so that these components will be correctly displayed by IDE's interface. Starting devenv with this key will not open its main GUI window.

We do not employ RepPkg utility as part of PVS-Studio deployment, instead manually writing required data to the registry by using our stand-alone installer. We chose this method because we have no desire of being dependent on some external third-party tools and we want full control over the installation process. Still, we do use RegPkg during plug-in development for convenient debugging.

VSIX packages

Beginning from Visual Studio 2010, VSPackage deployment process can be significantly simplified through the usage of VSIX packages. VSIX package itself is a common (Open Packaging Conventions) archive containing plug-in's binary files and all of the other auxiliary files which are necessary for plug-in's deployment. By passing such archive to the standard VSIXInstaller.exe utility, its contents will be automatically registered in the IDE:

VSIXInstaller.exe MyPackage.vsix

VSIX installer could also be used with /uninstall switch to remove the previously installed package from a system. A unique GUID of the extension package should be used to identify such package:

VSIXInstaller.exe /uninstall: 009084B1-6271-4621-A893-6D72F2B67A4D

Contents of a VSIX container are defined through the special vsixmanifest file, which should be added to plug-in's project. Vsixmanifest file permits the following properties to be defined for an extension:
  • targeted Visual Studio versions and editions, which will be supported by the plug-in;
  • a unique GUID identifier;
  • a list of components to be registered (VSPackage, MEF components, toolbox control etc.);
  • general information about the plug-in to be installed (description, license, version, etc.);
To include additional files into a VSIX container, the IncludeInVSIX node should be added to their declarations inside your MSBuild project (alternately, they could also be marked as included into VSIX from their respective property windows, by opening it from Visual Studio Solution Explorer).

<Content Include="MyPackage.pdb">
  <IncludeInVSIX>true</IncludeInVSIX>
</Content>

In fact, the VSIX file could be viewed as an almost full-fledged installer for extension packages on the latest versions of Visual Studio (2010 and 2012), allowing the extensions to be deployed by a "one-click" method. Publishing your VSIX container in the official Visual Studio Gallery for extensions allows end-users to install such packages through the Tools -> Extension Manager IDE dialog.

VSIX allows the extension to be deployed either for one of the regular Visual Studio editions, or for the isolated\integrated shell based distributions. In case of developing an extension for isolated shell application, instead of the Visual Studio version the VSIX manifest file should contain a special identification string for the targeted environment. For example, the identification string for Atmel Studio 6.1 should be "AtmelStudio, 6.1". But, if the extension you are developing utilizes only common automation model interfaces (such as the ones for the text editor, abstract project tree and so on), and does not require any of the specific ones (for example, interfaces for Visual C++ projects), then it is possible for you to specify several different editions of Visual Studio, as well as an isolated shell based ones, in the manifest file. This, in turn, will permit you to use a single installer for a wide range of Visual Studio based applications.

This new VSIX installation procedure in Visual Studio 2010 does substantially alleviate package deployment for end-users (as well as for developers themselves). Some developers even had decided to support only VS2010 IDE and versions above it, if only not to get involved with the development of a package and installer for earlier IDE versions.

Unfortunately, several issues can be encountered when using VSIX installer together with Visual Studio 2010 extension manager interface. For instance, sometimes the extension's binary files are not removed correctly after uninstall, which in turn blocks the VSIX installer from installing/reinstalling the same extension. As such, we advise you not to depend upon the VSIX installer entirely and to provide some backup, for example by directly removing your files from a previous plug-in installation before proceeding with a new one.

Package Load Key

Each VSPackage module loaded into Visual Studio must possess a unique Package Load Key (PLK). PLK key is specified through the ProvideLoadKey attribute for the Package subclass in 2005/2008 versions of the IDE.

[ProvideLoadKey("Standard", "9.99", "MyPackage", "My Company", 100)]

Starting with Visual Studio 2010, the presence of a PLK, as well as of the ProvideLoadKey attribute respectively, in a package is not required, but it can still be specified in case the module under development is targeting several versions of MSVS. The PLK can be obtained by registering at the Visual Studio Industry Partner portal, meaning it guarantees that the development environment can load only packages certified by Microsoft.

However, systems containing Visual Studio SDK installed are exceptions to this, as Developer License Key is installed together with the SDK. It allows the corresponding IDE to load any extension package, regardless of validity of its PLK.

Considering the aforementioned, one more time it is necessary to stress the importance of testing the distribution on a system without Visual Studio SDK present, because the extension package will operate properly on developer's workstation regardless of its PLK correctness.

Extension registration specifics in the context of supporting several different versions of Visual Studio IDE

By default, VSPackage project template will generate an extensibility project for the version of Visual Studio that is used for the development. This is not a mandatory requirement though, so it is possible to develop an extension for a particular version of IDE using a different one. It also should be noted that after automatically upgrading a project file to a newer version through devenv /Upgrade switch, the targeted version of the IDE and its corresponding managed API libraries will remain unchanged, i.e. from a previous version of Visual Studio.

To change the target of the extension to another version of Visual Studio (or to register an extension into this version to be more precise), you should alter values passed to the DefaultRegistryRoot attribute (only for 2005/2008 IDE versions, as starting from Visual Studio 2010 this attribute is no longer required) or change the target version in the VSIX manifest file (for versions above 2008).

VSIX support appears only starting from Visual Studio 2010, so building and debugging the plug-in targeted for the earlier IDE version from within Visual Studio 2010 (and later) requires setting up all the aforementioned registration steps manually, without VSIX manifest. While changing target IDE version one should also not forget to switch referenced managed assemblies, which contain COM interface wrappers utilized by the plug-in, to the corresponding versions as well.

Altering the IDE target version of the plug-in affects the following Package subclass attributes:
  • the InstalledProductRegistration attribute does not support overloading of its constructor with a (Boolean, String, String, String) signature, starting from Visual Studio 2010;
  • the presence of DefaultRegistryRoot and ProvideLoadKey attributes is not mandatory starting from Visual Studio 2010, as similar values are now specified inside VSIX manifest;

References


  1. MSDN. Experimental Build.
  2. MSDN. How to: Register a VSPackage.
  3. MSDN. VSIX Deployment.
  4. MSDN. How to: Obtain a PLK for a VSPackage.
  5. MZ-Tools. Resources about Visual Studio .NET extensibility.
  6. MSDN. Creating Add-ins and Wizards.
  7. MSDN. Using a Custom Registration Attribute to Register an Extension.
  8. MSDN. Shell (Integrated or Isolated).


Visual Studio Automation Object Model. EnvDTE and Visual Studio Shell Interop interfaces.


This item contains an overview of Visual Studio Automation Object Model. Model's overall structure and the means of obtaining access to its interfaces through DTE/DTE2 top level objects are examined. Several examples of utilizing elements of the model are provided. Also discussed are the issues of using model's interfaces within multithreaded applications; an example of implementing such mechanism for multithreaded interaction with COM interfaces in managed code is provided as well.

Introduction


Visual Studio development environment is built upon the principles of automation and extensibility, providing the developers using it with the ability of integrating almost any custom element into the IDE and allowing for an easy interaction with its default and user-created components. As the means of implementing these tasks, Visual Studio users are provided with several cross-complementing toolsets, the most basic and versatile among these is the Visual Studio Automation Object Model.

Automation Object Model is represented by a series of libraries containing a vast and well-structured API set which covers all aspects of IDE automation and the majority of its extensibility capabilities. Although, in comparison to other IDE extensibility tools, this model does not provide access to some portions of Visual Studio (this applies mostly to the extension of some IDE's features), it is nonetheless the most flexible and versatile among them.

The majority of the model's interfaces are accessible from within every type of IDE extension module, which allows interacting with the environment even from an external independent process. Moreover, the model itself could be extended along with the extension of Visual Studio IDE, providing other third-party developers with an access to user-created custom components.

Automation Object Model structure


Visual Studio automation model is composed of several interconnected functional object groups covering all aspects of the development environment; it also provides capabilities for controlling and extending these groups. Accessing any of them is possible through the top-level global DTE interface (Development Tools Environment). Figure 1 shows the overall structure of the automation model and how it is divided among functionality groups.


image1.png
Figure 1 — Visual Studio Automation Object Model (click the picture to zoom in)


The model itself could be extended by user in one of the following groups:
  • project models (implementing new project types, support for new languages);
  • document models (implementing new document types and document editors)
  • code editor level models (support for specific language constructs)
  • project build-level models
Automation model could be extended from plug-ins of VSPackage type only.

All of the automation model's interfaces could be conventionally subdivided into two large groups. 1st group are the interfaces of the EnvDTE and Visual Studio Interop namespaces, these interfaces allow interactions with basic common components of the IDE itself, such as tool windows, editors, event handling services and so on. 2nd group are the interfaces of the specific project model. The figure above specifies this interface group as late-bound properties, i.e. these interfaces are implemented in a separate dynamically loaded library. Each standard (i.e. the one that is included in a regular Visual Studio distribution) project model, such as Visual C++ or Visual Basic, provides a separate implementation for these interfaces. Third-party developers are able to extend the automation model by adding their own custom project models and by providing an implementation of these automation interfaces.

Also worth noting is that the interfaces of the 1st group, which was specified above, are universal, meaning that they could be utilized for interaction with any of the project models or Visual Studio editions, including the integrated\isolated Visual Studio shells. In this article we will examine this group in more detail.

But still, despite the model's versatility, not every group belonging to the model could be equally utilized from all the types of IDE extensions. For instance, some of the model's capabilities are inaccessible to external processes; these capabilities are tied to specific extension types, such as Add-In or VSPackage. Therefore, when selecting the type for the extension to be developed, it is important to consider the functionality that this extension will require.

The Microsoft.VisualStudio.Shell.Interop namespace also provides a group of COM interfaces, which can be used to extend and automate Visual Studio application from managed code. Managed Package Framework (MPF) classes, which we utilized earlier for creating a VSPackage plugin, are actually themselves based on these interfaces. Although theses interfaces are not a part of EnvDTE automation model described above, nevertheless they greatly enhance this model by providing additional functionality for VSPackage extensions, which is otherwise unavailable for extensions of other types.

Obtaining references to DTE/DTE2 objects.


In order to create a Visual Studio automation application it is necessary to obtain access to the automation objects themselves in the first place. To accomplish this, first of all it is necessary to hook up the correct versions of libraries containing the required managed API wrappers in the EnvDTE namespace. Secondly, the reference to the automation model top-level object, that is the DTE2 interface, should be obtained.

In the course of Visual Studio evolution, several of its automation objects had been modified or received some additional functionality. So, to maintain a backward compatibility with existing extension packages, new EnvDTE80, EnvDTE90, EnvDTE100 etc. namespaces were created instead of updating the interfaces from the original EnvDTE namespace. The majority of such updated interfaces from these new namespaces do maintain the same names as in the original ones, but with addition of an ordinal number at the end of the name, for example Solution and Solution2. It is advised that these updated interfaces should be utilized when creating a new project, as they do contain the most recent functionality. It's worth noting that properties and methods of DTE2 interface usually return object references with types corresponding to the original DTE, i.e. accessing dte2.Solution will return Solution and not the Solution2 as it would seem.

Although these new EnvDTE80, EnvDTE90, EnvDTE100 namespaces do contain some of the updated functionality as mentioned above, still it is the EnvDTE interface that contains the majority of automation objects. Therefore, in order to possess access to all of the existing interfaces, it is necessary to link all versions of the managed COM wrapper libraries to the project, as well as to obtain the references to DTE and also to DTE2.

The way of obtaining top-level EnvDTE object reference is dependent upon the type of IDE extension being developed. Let's examine 3 of such extension types: Add-In, VSPackage and an MSVS-independent external process.

Add-In extension

In the case of an Add-In extension, access to the DTE interface can be obtained inside the OnConnection method which should be implemented for the IDTExtensibility interface that provides access to the extension-environment interaction events. The OnConnection method is called at the moment when the module is loaded by the IDE; it can happen either when the environment is being loaded itself or after the extension was called for the first time in the IDE session. The example of obtaining the reference follows:

public void OnConnection(object application,
  ext_ConnectMode connectMode, object addInInst, ref Array custom)
           {
               _dte2 = (DTE2)application;
               ...
           }

An Add-In module can be initialized either at the moment of IDE start-up, or when it is called for the first time in current IDE session. So, the connectMode can be used to correctly determine the moment of initialization inside the OnConnection method.

switch(connectMode)
{
    case ext_ConnectMode.ext_cm_UISetup:
        ...
        break;

    case ext_ConnectMode.ext_cm_Startup:
        ...
        break;

    case ext_ConnectMode.ext_cm_AfterStartup:
        ...
        break;

    case ext_ConnectMode.ext_cm_CommandLine:
        ...
        break;
}

As in the example above, Add-In could be loaded either simultaneously with the IDE itself (if the startup option in the Add-In manager is checked), when it is called the first time or when it is called through the command line. The ext_ConnectMode.ext_cm_UISetup option is invoked only for a single time in the plug-in's overall lifetime, which is during its first initialization. This case should be used for initializing user UI elements which are to be integrated into the environment (more on this later on).

If an Add-In is being loaded during Visual Studio start-up (ext_ConnectMode.ext_cm_Startup), then at the moment OnConnect method receives control for the first time, it is possible that the IDE still is not fully initialized itself. In such a case, it is advised to postpone the acquisition of the DTE reference until the environment is fully loaded. The OnStartupComplete handler provided by the IDTExtensibility can be used for this.

public void OnStartupComplete(ref Array custom)
{
    ...
}

VSPackage extension

For VSPackage type of extension, the DTE could be obtained through the global Visual Studio service with the help of GetService method of a Package subclass:

DTE dte = MyPackage.GetService(typeof(DTE)) as DTE;

Please note that the GetService method could potentially return null in case Visual Studio is not fully loaded or initialized at the moment of such access, i.e. it is in the so called "zombie" state. To correctly handle this situation, it is advised that the acquisition of DTE reference should be postponed until this interface is inquired. But in case the DTE reference is required inside the Initialize method itself, the IVsShellPropertyEvents interface can be utilized (also by deriving our Package subclass from it) and then the reference could be safely obtained inside the OnShellPropertyChange handler.

DTE dte;
uint cookie;
 
protected override void Initialize()
{
  base.Initialize();

  IVsShell shellService = GetService(typeof(SVsShell)) as IVsShell;
  if (shellService != null)
    ErrorHandler.ThrowOnFailure(
      shellService.AdviseShellPropertyChanges(this,out cookie));
...
}
 
public int OnShellPropertyChange(int propid, object var)
{
  // when zombie state changes to false, finish package initialization
  if ((int)__VSSPROPID.VSSPROPID_Zombie == propid)
  {
    if ((bool)var == false)
    {
     this.dte = GetService(typeof(SDTE)) as DTE;
     IVsShell shellService = GetService(typeof(SVsShell)) as IVsShell;

     if (shellService != null)
       ErrorHandler.ThrowOnFailure(
         shellService.UnadviseShellPropertyChanges(this.cookie) );
     this.cookie = 0;
    }
  }
  return VSConstants.S_OK;
}

It should be noted that the process of VSPackage module initialization at IDE startup could vary for different Visual Studio versions. For instance, in case of VS2005 and VS2008, an attempt at accessing DTE during IDE startup will almost always result in null being returned, owning to the relative fast loading times of these versions. But, one does not simply obtain access into DTE. In Visual Studio 2010 case, it mistakenly appears that one could simply obtain an access to the DTE from inside the Initialize() method. In fact, this impression is a false one, as such method of DTE acquisition could potentially cause the occasional appearance of "floating" errors which are hard to identify and debug, and even the DTE itself may be still uninitialized when the reference is acquired. Because of these disparities, the aforementioned acquisition method for handling IDE loading states should not be ignored on any version of Visual Studio.

Independent external process

The DTE interface is a top-level abstraction for Visual Studio environment in the automation model. In order to acquire a reference to this interface from an external application, its ProgID COM identifier could be utilized; for instance, it will be "VisualStudio.DTE.10.0" for Visual Studio 2010. Consider this example of initializing a new IDE instance and when obtaining a reference to the DTE interface.

// Get the ProgID for DTE 8.0.
System.Type t = System.Type.GetTypeFromProgID(
    "VisualStudio.DTE.10.0", true);
// Create a new instance of the IDE.
object obj = System.Activator.CreateInstance(t, true);
// Cast the instance to DTE2 and assign to variable dte.
EnvDTE80.DTE2 dte = (EnvDTE80.DTE2)obj;
// Show IDE Main Window
dte.MainWindow.Activate();

In the example above we've actually created a new DTE object, starting deven.exe process by the CreateInstance method. But at the same time, the GUI window of the environment will be displayed only after the Activate method is called.

Next, let's review a simple example of obtaining the DTE reference from an already running Visual Studio Instance:

EnvDTE80.DTE2 dte2;
dte2 = (EnvDTE80.DTE2)
  System.Runtime.InteropServices.Marshal.GetActiveObject(
    "VisualStudio.DTE.10.0");

However, in case several instances of the Visual Studio are executing at the moment of our inquiry, the GetActiveObject method will return a reference to the IDE instance that was started the earliest. Let's examine a possible way of obtaining the reference to DTE from a running Visual Studio instance by the PID of its process.

using EnvDTE80;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;

[DllImport("ole32.dll")]
private static extern void CreateBindCtx(int reserved, 
                                         out IBindCtx ppbc);
[DllImport("ole32.dll")]
private static extern void GetRunningObjectTable(int reserved, 
  out IRunningObjectTable prot);

public static DTE2 GetByID(int ID)
{
  //rot entry for visual studio running under current process.
  string rotEntry = String.Format("!VisualStudio.DTE.10.0:{0}", ID);
  IRunningObjectTable rot;
  GetRunningObjectTable(0, out rot);
  IEnumMoniker enumMoniker;
  rot.EnumRunning(out enumMoniker);
  enumMoniker.Reset();
  IntPtr fetched = IntPtr.Zero;
  IMoniker[] moniker = new IMoniker[1];
  while (enumMoniker.Next(1, moniker, fetched) == 0)
  {
    IBindCtx bindCtx;
    CreateBindCtx(0, out bindCtx);
    string displayName;
    moniker[0].GetDisplayName(bindCtx, null, out displayName);
    if (displayName == rotEntry)
    {
      object comObject;
      rot.GetObject(moniker[0], out comObject);
      return (EnvDTE80.DTE2)comObject;
    }
  }
  return null;
}

Here we've acquired the DTE interface by identifying the required instance of the IDE in the table of running COM objects (ROT, Running Object Table) by its process identifier. Now we can access the DTE for all of the executing instances of Visual Studio, for example:

Process Devenv;
...
//Get DTE by Process ID
EnvDTE80.DTE2 dte2 = GetByID(Devenv.Id);

Additionally, to acquire any project-specific interface (including custom model extensions), for example the CSharpProjects model, through a valid DTE interface, the GetObject method should be utilized:

Projects projects = (Projects)dte.GetObject("CSharpProjects");

The GetObject method will return a Projects collection of regular Project objects, and each one of them will contain a reference to our project-specific properties, among other regular ones.

Visual Studio text editor documents


Automation model represents Visual Studio text documents through the TextDocument interface. For example, C/C++ source code files are opened by the environment as text documents. TextDocument is based upon the common automation model document interface (the Document interface), which represents file of any type opened in Visual Studio editor or designer. A reference to the text document object can be obtained through the Object field of the Document object. Let's acquire a text document for the currently active (i.e. the one possessing focus) document from IDE's text editor.

EnvDTE.TextDocument objTextDoc =
(TextDocument)PVSStudio.DTE.ActiveDocument.Object("TextDocument");

Modifying documents

The TextSelection document allows controlling text selection or to modify it. The methods of this interface represent the functionality of Visual Studio text editor, i.e. they allow the interaction with the text as it presented directly by the UI.

EnvDTE.TextDocument Doc =
  (TextDocument)PVSStudio.DTE.ActiveDocument.Object(string.Empty);
Doc.Selection.SelectLine();
TextSelection Sel = Doc.Selection;
int CurLine = Sel.TopPoint.Line;
String Text = Sel.Text;
Sel.Insert("test\r\n");

In this example we selected a text line under the cursor, read the selected text and replaced it with a 'test' string.

TextDocument interface also allows text modification through the EditPoint interface. This interface is somewhat similar to the TextSelection, but instead of operating with the text through the editor UI, it directly manipulates text buffer data. The difference between them is that the text buffer is not influenced by such editor-specific notions as WordWrap and Virtual Spaces. It should be noted that both of these editing methods are not able to modify read-only text blocks.

Let's examine the example of modifying text with EditPoint by placing additional lines at the end of current line with a cursor.

objEditPt = objTextDoc.StartPoint.CreateEditPoint();
int lineNumber = objTextDoc.Selection.CurrentLine;
objEditPt.LineDown(lineNumber - 1);

EditPoint objEditPt2 = objTextDoc.StartPoint.CreateEditPoint();
objEditPt2.LineDown(lineNumber - 1);
objEditPt2.CharRight(objEditPt2.LineLength);

String line = objEditPt.GetText(objEditPt.LineLength);
String newLine = line + "test";
objEditPt.ReplaceText(objEditPt2, newLine,
  (int)vsEPReplaceTextOptions.vsEPReplaceTextKeepMarkers);

Navigating the documents

VSPackage modules are able to obtain access to a series of global services which could be used for opening and handling environment documents. These services could be acquired by the Package.GetGlobalService() method from Managed Package Framework. It should be noted that the services described here are not part of the EnvDTE model and are accessible only from a Package-type extension, and therefore they could not be utilized in other types of Visual Studio extensions. Nonetheless, they can be quite useful for handling IDE documents when they are utilized in addition to the Documents interface described earlier. Next, we'll examine these services in more detail.

The IVsUIShellOpenDocument interface controls the state of documents opened in the environment. Following is the example that uses this interface to open a document through path to a file which this document will represent.

String path = "C:\Test\test.cpp";
IVsUIShellOpenDocument openDoc =
  Package.GetGlobalService(typeof(IVsUIShellOpenDocument))
    as IVsUIShellOpenDocument;

IVsWindowFrame frame;
Microsoft.VisualStudio.OLE.Interop.IServiceProvider sp;
IVsUIHierarchy hier;
uint itemid;
Guid logicalView = VSConstants.LOGVIEWID_Code;
if (ErrorHandler.Failed(
  openDoc.OpenDocumentViaProject(path, ref logicalView, out sp, 
    out hier, out itemid, out frame))
      || frame == null)
{
  return;
}
object docData;
frame.GetProperty((int)__VSFPROPID.VSFPROPID_DocData, out docData);

The file will be opened in a new editor or will receive focus in case it already has been opened earlier. Next, let's read a VsTextBuffer text buffer from this document we opened:

// Get the VsTextBuffer  
VsTextBuffer buffer = docData as VsTextBuffer;
if (buffer == null)
{
  IVsTextBufferProvider bufferProvider = docData as
    IVsTextBufferProvider;
  if (bufferProvider != null)
  {
    IVsTextLines lines;
    ErrorHandler.ThrowOnFailure(bufferProvider.GetTextBuffer(
      out lines));
    buffer = lines as VsTextBuffer;
    Debug.Assert(buffer != null, 
      "IVsTextLines does not implement IVsTextBuffer");
    if (buffer == null)
    {
      return;
    }
  }
} 

The IVsTextManager interface controls all of the active text buffers in the environment. For example we can navigate a text document using the NavigateToLineAndColumn method of this manager on a buffer we've acquired earlier:

IVsTextManager mgr = Package.GetGlobalService(typeof(VsTextManagerClass))
  as IVsTextManager;
mgr.NavigateToLineAndColumn(buffer, ref logicalView, line, 
  column, line, column);

Subscribing and handling events


Automation objects events are represented by the DTE.Events property. This element references all of the common IDE events (such as CommandEvents, SolutionEvents), as well as the events of separate environment components (project types, editors, tools etc.), also including the ones designed by third-party developers. To acquire a reference for this automation object, the GetObject method could be utilized.

When subscribing to the DTE events one should remember that this interface could be still unavailable at the moment of extension being initialized. So it is always important to consider the sequence of your extension initialization process if the access to DTE.Events is required in the Initialize() method of your extension package. The correct handling of initialization sequence will vary for different extension types, as it was described earlier.

Let's acquire a reference for an events object of Visual C++ project model defined by the VCProjectEngineEvents interface and assign a handler for the removal of an element from the Solution Explorer tree:

VCProjectEngineEvents m_ProjectItemsEvents =
  PVSStudio.DTE.Events.GetObject("VCProjectEngineEventsObject") 
    as VCProjectEngineEvents;
m_ProjectItemsEvents.ItemRemoved +=
  new _dispVCProjectEngineEvents_ItemRemovedEventHandler(
    m_ProjectItemsEvents_ItemRemoved);

MDI windows events

The Events.WindowEvents property could be utilized to handle regular events of an environment MDI window. This interface permits the assignment of a separate handler for a single window (defined through the EnvDTE.Window interface) or the assignment of a common handler for all of the environment's windows. Following example contains the assignment of a handler for the event of switching between IDE windows:

WindowEvents WE = PVSStudio.DTE.Events.WindowEvents;
WE.WindowActivated += 
  new _dispWindowEvents_WindowActivatedEventHandler(
    Package.WE_WindowActivated);

Next example is the assignment of a handler for window switching to the currently active MDI window through WindowEvents indexer:

WindowEvents WE = m_dte.Events.WindowEvents[MyPackage.DTE.ActiveWindow];
WE.WindowActivated += new
  _dispWindowEvents_WindowActivatedEventHandler(
    MyPackage.WE_WindowActivated);

IDE commands events

The actual handling of environment's commands and their extension through the automation model is covered in a separate article of this series. In this section we will examine the handling of the events related to these commands (and not of the execution of the commands themselves). Assigning the handlers to these events is possible through the Events.CommandEvents interface. The CommandEvents property, as in the case of MDI windows events, also permits the assignment of a handler either for all of the commands or for a single one through the indexer.

Let's examine the assignment of a handler for the event of a command execution being complete (i.e. when the command finishes its execution):

CommandEvents CEvents = DTE.Events.CommandEvents;
CEvents.AfterExecute += new
  _dispCommandEvents_AfterExecuteEventHandler(C_AfterExecute);

But in order to assign such a handler for an individual command, it is necessary to identify this command in the first place. Each command of the environment is identified by a pair of GUID:ID, and in case of a user-created commands these values are specified directly by the developer during their integration, for example through the VSCT table. Visual Studio possesses a special debug mode which allows identifying any of the environment's commands. To activate this mode, it is required that the following key is to be added to the system registry (an example for Visual Studio 2010):

[HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\10.0\General]
"EnableVSIPLogging"=dword:00000001

Now, after restarting the IDE, hovering your mouse over menu or toolbar elements with CTRL+SHIFT being simultaneously pressed (though sometime it will not work until you left-click it) will display a dialog window containing all of the command's internal identifiers. We are interested in the values of Guid and CmdID. Let's examine the handling of events for the File.NewFile command:

CommandEvents CEvents = DTE.Events.CommandEvents[
  "{5EFC7975-14BC-11CF-9B2B-00AA00573819}", 221];
CEvents.AfterExecute += new  
  _dispCommandEvents_AfterExecuteEventHandler(C_AfterExecute);

The handler obtained in this way will receive control only after the command execution is finished.

void C_AfterExecute(string Guid, int ID, object CustomIn, 
  object CustomOut)
{
  ...
}

This handler should not be confused with an immediate handler for the execution of the command itself which could be assigned during this command's initialization (from an extension package and in case the command is user-created). Handling the IDE commands is described in a separate article that is entirely devoted to IDE commands.

In conclusion to this section it should be mentioned that in the process of developing our own VSPackage extension, we've encountered the necessity to store the references to interface objects containing our handler delegates (such as CommandEvents, WindowEvents etc.) on the top-level fields of our main Package subclass. The reason for this is that in case of the handler being assigned through a function-level local variable, it is lost immediately after leaving the method. Such behavior could probably be attributed to the .NET garbage collector, although we've obtained these references from the DTE interface which definitely exists during the entire lifetime of our extension package.

Handling project and solution events (for VSPackage extensions)


Let's examine some of the interfaces from the Microsoft.VisualStudio.Shell.Interop namespace, the ones that permit us to handle the events related to Visual Studio projects and solution to be more precise. Although these interfaces are not a part of EnvDTE automation model, they could be implemented by the main class of VSPackage extension (that is the class that was inherited from Package base class of Managed Package Framework). That is why, if you are developing the extension of this type, these interfaces a conveniently supplement the basic set of interfaces provided by the DTE object. By the way, this is another argument for creating a full-fledged VSPackage plugin using MPF.

The IVsSolutionEvents could be implemented by the class inherited from Package and it is available starting from Visual Studio version 2005, and the isolated\integrated shells based applications. This interface permits you to track the loading, unloading, opening and closing of projects or even the whole solutions in the development environment by implementing such of its methods as OnAfterCloseSolution, OnBeforeCloseProject, OnQueryCloseSolution. For example:

public int OnAfterLoadProject(IVsHierarchy pStubHierarchy, IVsHierarchy pRealHierarchy)
        {
//your custom handler code            
return VSConstants.S_OK;
        }

As you can see, this method takes the IVsHierarchy object as an input parameter which represents the loading project. Managing of such objects will be examined in another article devoted to the interaction with Visual Studio project model.

The IVsSolutionLoadEvents interface, in a similar fashion to the interface described above, should be implemented by the Package subclass and is available to versions of Visual Studio starting from 2010 and above. This interface allows you to handle such interesting aspects as batch loading of project groups and background solution loadings (the OnBeforeLoadProjectBatch and OnBeforeBackgroundSolutionLoadBegins methods), and also to intercept the end of this background loading operation as well (the OnAfterBackgroundSolutionLoadComplete method).

Such event handlers should come in handy in case your plug-in needs to execute some code immediately after its initialization, and, at the same time, the plug-in depends on projects\solutions that are loaded inside the IDE. In this a case, executing such a code without waiting for the solution loading to be finished could lead to either incorrect (incomplete) results because of the incompletely formed projects tree, or even to runtime exceptions.

While developing PVS-Studio IDE plug-in, we've encountered another interesting aspect of VSPackage plug-in initialization. Then one Package plug-in enters a waiting state (for instance, by displaying a dialog window to the user), further initialization of VSPackage extensions is suspended until the blocking plug-in returns. So, when handling loading and initialization inside the environment, one should always remember this possible scenario as well.

And finally, I want to return one final time to the fact, that for the interface methods described above to operate correctly, you should inherit your main class from theses interfaces:

class MyPackage: Package, IVsSolutionLoadEvents, IVsSolutionEvents
{
//Implementation of Package, IVsSolutionLoadEvents, IVsSolutionEvents
...
}

Supporting Visual Studio color schemes


If the extension you are developing will be integrated into the interface of the development environment, for instance, by creating custom tool windows or document MDI windows (and the most convenient way for such an integration is a VSPackage extesnion), it is advisable that the coloring of your custom UI components should match the common color scheme used by Visual Studio itself.

The importance of this task was elevated with the release of Visual Studio 2012, containing two hugely opposite color themes (Dark and Light) which the user could switch "on the fly" from the IDE options window.

The GetVSSysColorEx method from Visual Studio Interop interface IVsUIShell2 could be utilized to obtain environment's color settings. This interface is available to VSPackage plugins only.

IVsUIShell2 vsshell = this.GetService(typeof(SVsUIShell)) as IVsUIShell2;

By passing the the __VSSYSCOLOREX and __VSSYSCOLOREX3 enums to the GetVSSysColorEx method, you can get the currently selected color for any of Visual Studio UI elements. For example, let's obtain one of the colors from the context menu's background gradient:

uint Win32Color;
vsshell.GetVSSysColorEx((int)__VSSYSCOLOREX3.VSCOLOR_COMMANDBAR_MENU_BACKGROUND_GRADIENTBEGIN, out Win32Color);
Color BackgroundGradient1 = ColorTranslator.FromWin32((int)Win32Color);

Now we can use this Color object to "paint" our custom context menus. To determine the point in time at which the color theme of your components should be reapplied, you can, for example, utilize events of the environment command responsible for opening of IDE's settings window (Tools -> Options). How to subscribe your handlers to such an event was described earlier in this article.

But if you are, for some reason, unable to utilize the IVsUIShell2 object (for instance, in case you are developing a non-VSPackage extension), but at the same time you still need to support Visual Studio color themes, then it is possible to obtain color values for environment's various UI components directly from the system registry. We will not cover this approach in the article, but here you can download a free and open-source tool designed for Visual Studio color theme editing. The tool is written in C# and it contains all the code required for reading and modifying Visual Studio 2012 color themes from the managed code.

Interacting with COM interfaces from within a multithreaded application


Initially PVS-Studio extension package had not contained any specific thread-safety mechanisms for its interaction with Visual Studio APIs. At the same time, we had been attempting to confine the interactions with this APIs within a single background thread which was created and owned by our plug-in. And such approach functioned flawlessly for quite a long period. However, several bug reports from our users, each one containing a similar ComExeption error, prompted us to examine this issue in more detail and to implement a threading safety mechanism for our COM Interop.

Although Visual Studio automation model is not a thread-safe one, it still provides a way for interacting with multi-threaded applications. Visual Studio application is a COM (Component Object Mode) server. For the task of handling calls from COM clients (in our case, this will be our extension package) to thread-unsafe servers, COM provides a mechanism known as STA (Single-Threaded Apartment) model. In the terms of COM, an Apartment represents a logical container inside a process in which objects and threads share the same thread access rules. STA can hold only a single thread, but an unlimited number of objects, inside such container. Calls from other threads to such thread-unsafe objects inside STA are converted into messages and posted to a message queue. Messages are retrieved from the message queue and converted back into method calls one at a time by the thread running in the STA, so it becomes possible for only a single thread to access these unsafe objects on the server.

Utilizing Apartment mechanism inside managed code

The .NET Framework does not utilize COM Apartment mechanics directly. Therefore, when a managed application calls a COM object in the COM interoperation scenarios, CLR (Common Language Runtime) creates and initializes Apartment container. A managed thread is able to create and enter either an MTA (Multi-Threaded Apartment, a container that, contrary to STA, can host several threads at the same time), or an STA, though a thread will be started as an MTA by default. The type of the Apartment could be specified before thread is launched:

Thread t = new Thread(ThreadProc);
t.SetApartmentState(ApartmentState.STA);
...
t.Start();

As an Apartment type could not be changed once thread had been started, the STAThread attribute should be used to specify the main thread of a managed application as an STA:

[STAThread]
static void Main(string[] args)
{...}

Implementing message filter for COM interoperation errors in a managed environment

As STA serializes all of calls to the COM server, one of the calling clients could potentially be blocked or even rejected when the server is busy, processing different calls or another thread is already inside the apartment container. In case COM server rejects its client, .NET COM interop will generate a System.Runtime.InteropServices.COMException ("The message filter indicated that the application is busy").

When working on a Visual Studio module (add-in, vspackage) or a macro, the execution control usually passes into the module from the environment's main STA UI thread (such as in case of handling events or environment state changes, etc.). Calling automation COM interfaces from this main IDE thread is safe. But if other background threads are planned to be utilized and EnvDTE COM interfaces are to be called from these background threads (as in case of long calculations that could potentially hang the IDE's interface, if these are performed on the main UI thread), then it is advised to implement a mechanism for handling calls rejected by a server.

While working on PVS-Studio plug-in we've often encountered these kinds of COM exceptions in situations when other third-party extensions were active inside the IDE simultaneously with PVS-Studio plug-in. Heavy user interaction with the UI also was the usual cause for such issues. It is quite logical that these situations often resulted in simultaneous parallel calls to COM objects inside STA and consequently to the rejection of some of them.

To selectively handle incoming and outgoing calls, COM provides the IMessageFilter interface. If it is implemented by the server, all of the calls are passed to the HandleIncomingCall method, and the client is informed on the rejected calls through the RetryRejectedCall method. This in turn allows the rejected calls to be repeated, or at least to correctly present this rejection to a user (for example, by displaying a dialog with a 'server is busy' message). Following is the example of implementing the rejected call handling for a managed application.

[ComImport()]
[Guid("00000016-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IMessageFilter
{
  [PreserveSig]
  int HandleInComingCall(
    int dwCallType,
    IntPtr hTaskCaller,
    int dwTickCount,
    IntPtr lpInterfaceInfo);

  [PreserveSig]
  int RetryRejectedCall(
    IntPtr hTaskCallee,
    int dwTickCount,
    int dwRejectType);

  [PreserveSig]
  int MessagePending(
    IntPtr hTaskCallee,
    int dwTickCount,
    int dwPendingType);
}

class MessageFilter : MarshalByRefObject, IDisposable, IMessageFilter
{

  [DllImport("ole32.dll")]
  [PreserveSig]
  private static extern int CoRegisterMessageFilter(
    IMessageFilter lpMessageFilter, 
    out IMessageFilter lplpMessageFilter);

  private IMessageFilter oldFilter;
  private const int SERVERCALL_ISHANDLED = 0;
  private const int PENDINGMSG_WAITNOPROCESS = 2;
  private const int SERVERCALL_RETRYLATER = 2;

  public MessageFilter()
  {
    //Starting IMessageFilter for COM objects
    int hr =
      MessageFilter.CoRegisterMessageFilter(
        (IMessageFilter)this, 
         out this.oldFilter);
    System.Diagnostics.Debug.Assert(hr >= 0, 
      "Registering COM IMessageFilter failed!");
  }

  public void Dispose()
  {
    //disabling IMessageFilter
    IMessageFilter dummy;
    int hr = MessageFilter.CoRegisterMessageFilter(this.oldFilter, 
                                                   out dummy);
    System.Diagnostics.Debug.Assert(hr >= 0, 
      "De-Registering COM IMessageFilter failed!")
    System.GC.SuppressFinalize(this);
  }

  int IMessageFilter.HandleInComingCall(int dwCallType, 
    IntPtr threadIdCaller, int dwTickCount, IntPtr lpInterfaceInfo)
  {
    // Return the ole default (don't let the call through).
    return MessageFilter.SERVERCALL_ISHANDLED;
  }

  int IMessageFilter.RetryRejectedCall(IntPtr threadIDCallee, 
    int dwTickCount, int dwRejectType)
  {
    if (dwRejectType == MessageFilter.SERVERCALL_RETRYLATER)
    {
      // Retry the thread call immediately if return >=0 & 
      // <100.
      return 150; //waiting 150 mseconds until retry
    }
    // Too busy; cancel call. SERVERCALL_REJECTED
    return -1;
    //Call was rejected by callee. 
    //(Exception from HRESULT: 0x80010001 (RPC_E_CALL_REJECTED))
  }

  int IMessageFilter.MessagePending(
      IntPtr threadIDCallee, int dwTickCount, int dwPendingType)
  {
    // Perform default processing.
    return MessageFilter.PENDINGMSG_WAITNOPROCESS;
  }
}

Now we can utilize our MessageFilter while calling COM interfaces from a background thread:

using (new MessageFilter())
{
  //COM-interface dependent code
  ...
}

References


  1. MSDN. Referencing Automation Assemblies and the DTE2 Object.
  2. MSDN. Functional Automation Groups.
  3. MZ-Tools. HOWTO: Use correctly the OnConnection method of a Visual Studio add-in.
  4. The Code Project. Understanding The COM Single-Threaded Apartment.
  5. MZ-Tools. HOWTO: Add an event handler from a Visual Studio add-in.
  6. Dr. eX's Blog. Using EnableVSIPLogging to identify menus and commands with VS 2005 + SP1.


Visual Studio commands


This item deals with creation, utilization and handling of Visual Studio commands in its extension modules through automation object model APIs and IDE services. The relations between IDE commands and environment UI elements, such as user menus and toolbars, will also be examined.

Introduction


Visual Studio commands provide a way for direct interaction with development environment through the keyboard input. Almost all capabilities of different dialog and tool windows, toolbars and user menus are represented by the environment's commands. In fact, main menu items and toolbar buttons are practically commands themselves. Although it is possible for a command not to possess a direct representation in the development environment's UI, as commands are not the UI elements per se, they can be represented by such UI elements as menu items and toolbar buttons.

PVS-Studio IDE extension package integrates several subgroups of its commands into Visual Studio main menu, and these commands serve as one of the plug-in's main UI components (with another one being its MDI tool window), allowing a user to control all of the aspects of static code analysis either from the environment's UI or by invoking the commands directly through command line.

Using IDE commands


Any IDE command, regardless of its UI representation in the IDE (or of the lack of it), could be executed directly through the Command or Immediate windows, as well as by starting devenv.exe with the '/command' argument.

The full name of a command is formed according to its affiliation with a functional group, as for example the commands of the 'File' main menu item. Command's full name could be examined in the 'Keyboard, Environment' Options page. Also, the 'Tools -> Customize -> Commands' dialog allows inspecting all of the commands which are currently registered within the environment. This dialog sorts the commands by their respective functional groups and UI presentation types (menus, toolbars), also allowing to modify, add or delete them.

Commands can receive additional arguments which should be separated from the command's name by a space. Let's examine a call to a standard system command of the main menu, 'File -> New -> File' for example, with a passing of additional parameters to it through the Command Window:

>File.NewFile Mytext /t:"General\Text File" 
  /e:"Source Code (text) Editor"

A command's syntax generally complies with the following rules:
  • command's name and arguments are separated by a space
  • arguments containing spaces are wrapped by double quotes
  • The caret (^) is used as an escape character
  • One-character abridgments for command names can be combined, as for example, /case(/c) and /word(/w) could be presented as /cw
When using the 'command' command-line switch, name of a command with all of its arguments should be wrapped by double quotes:

devenv.exe /command "MyGroup.MyCommandName arg1 arg2"

For the sake of convenience, a command could be associated with an alias:

>alias MyAlias File.NewFile MyFile

Commands integrated into IDE by PVS-Studio extension can be utilized through the /command switch as well. For example, this mode could be used for the integration of our static analysis into the automated build process. Our analyzer itself (PVS-Studio.exe) is a native command-line application, which operates quite similar to the compiler, i.e. it takes a path to the file containing source code and its compilation arguments and then it outputs analysis results to stdout/stderr streams. It's quite obvious that the analyzer could easily be integrated directly into the build system (for instance, into a system which is based on MSBuild, NMake or even GNU Make) at the same level where C/C++ compiler is being called. Of course, such integration already provides us, by its own definition, with complete enumeration of all of the source files being built, with all of their compilation parameters. In turn, this allows for a substitution (or supplementation) of a compiler call by call to the analyzer. Although the described scenario is fully supported by PVS-Studio.exe analyzer, it still requires a complete understanding of build system's internals as well as an opportunity to modify a system in the first place, which could be problematic or even impossible at times.

Therefore, the integration of the analyzer into the build process can be performed in a more convenient way, on a higher level (i.e. at the level of Continuous Integration Server), by utilizing Visual Studio extension commands through the /command switch, for example, by using the PVS-Studio.CheckSolution command to perform analysis on MSVS solution. Of course, such use case is only possible when building Visual C++ native project types (vcproj/vcxproj).

In case Visual Studio is started from a command line, the /command switch will be executed immediately after the environment is fully loaded. In this case, the IDE will be started as a regular GUI application, without redirecting its standard I/O streams to the console that was used to launch the environment. It should be noted that, in general, Visual Studio is a UI based development environment and so it is not intended for command line operations. It is recommended to employ Microsoft MSBuild utility for building inside build automation systems, as this tool supports all of native Visual Studio project types.

Caution should be applied when using Visual Studio /command switch together with non-interactive desktop mode (for example when calling IDE from a Windows service). We've encountered several interesting issues ourselves when we were evaluating the possibility of integrating PVS-Studio static analysis into Microsoft Team Foundation build process, as Team Foundation operates as a Windows service by default. At that moment, our plug-in had not been tested for non-interactive desktop sessions and was incorrectly handling its child windows and dialogs, which in turn led to exceptions and crashes. But Visual Studio itself experienced none of such issues, almost none to be more precise. The case is, Visual Studio displays a particular dialog for every user when it is started for a first time after an installation, and this dialog offers the user to select a default UI configuration. And it was this dialog that Visual Studio displayed for a LocalSystem account, the account which actually owns the Team Foundation service. It turns out that the same dialog is 'displayed' even in the non-interactive desktop mode, and it subsequently blocks the execution of the /command switch. As this user doesn't have an interactive desktop, he is also unable to close this dialog normally by manually starting the IDE himself. But, in the end, we were able to close the dialog manually by launching Visual Studio for LocalSystem account in the interactive mode through psexec tool from PSTools utilities.

Creating and handling commands in VSPackage. Vsct files.


VSPackage extension utilizes Visual Studio command table (*.vsct) file for creating and managing commands that it integrates into the IDE. Command tables are text files in XML format which can be compiled by VSCT compiler into binary CTO files (Command Table Output). CTO files are then included as a resources into final builds of IDE extension packages. With the help of VSCT, commands can be associated with menus or toolbar buttons. Support for VSCT is available starting from Visual Studio 2005. Earlier IDE versions utilized CTC (Command Table Compiler) files handling their commands, but they will not be covered in this article.

In a VSCT file each command is assigned a unique ID — CommandID, a name, a group and a quick access hotkey combination, while its representation in the interface (if any) is specified by special flags.

Let's examine a basic structure of VSCT file. The root element of file is 'CommandTable' node that contains the 'Commands' sub-node, which defines all of the user's commands, groups, menu items, toolbars etc. Value of the "Package" attribute of the "Commands" node must correspond with the ID of your extension. The "Symbols" sub-node should contain definitions for all identifiers used throughout this VSCT file. The 'KeyBindings' sub-node contains default quick access hotkey combinations for the commands.

<CommandTable"http://schemas.microsoft.com/VisualStudio/2005-10-
18/CommandTable">

    <Extern href="stdidcmd.h"/>
    <Extern href="vsshlids.h"/>
  <Commands>
    <Groups>
    ...
    </Groups>
    <Bitmaps>
    ...
    </Bitmaps>
  </Commands>
  <Commands package="guidMyPackage">
    <Menus>
    ...
    </Menus>
    <Buttons>
    ...
    </Buttons>
  </Commands>

  <KeyBindings>
    <KeyBinding guid="guidMyPackage" id="cmdidMyCommand1"
 editor="guidVSStd97" key1="221" mod1="Alt" />
  </KeyBindings>
  <Symbols>
    <GuidSymbol name="guidMyPackage" value="{B837A59E-5BF0-4190-B8FC-
FDC35BE5C342}" />
    <GuidSymbol name="guidMyPackageCmdSet" value="{CC8B1E36-FE6B-48C1-
B9A9-2CC0EAB4E71F}">
      <IDSymbol name="cmdidMyCommand1" value="0x0101" />
    </GuidSymbol>
  </Symbols>
</CommandTable>

The 'Buttons' node defines the commands themselves by specifying their UI representation style and binding them to various command groups.

<Button guid="guidMyPackageCmdSet" id="cmdidMyCommand1"
priority="0x0102" type="Button">
  <Parent guid="guidMyPackageCmdSet" id="MyTopLevelMenuGroup" />
  <Icon guid="guidMyPackageCmdSet" id="bmpMyCommand1" />
  <CommandFlag>Pict</CommandFlag>
  <CommandFlag>TextOnly</CommandFlag>
  <CommandFlag>IconAndText</CommandFlag>
  <CommandFlag>DefaultDisabled</CommandFlag>
  <Strings>
    <ButtonText>My Command 1</ButtonText>
  </Strings>
</Button>

The 'Menus' node defines the structure of UI elements (such as menus and toolbars), also binding them to command groups in the 'Groups' node. A group of commands bound with a 'Menu' element will be displayed by the UI as a menu or a toolbar.

<Menu guid=" guidMyPackageCmdSet" id="SubMenu1" priority="0x0000"
type="Menu">
  <Parent guid="guidMyPackageCmdSet" id="MyTopLevelMenuGroup"/>
  <Strings>
    <ButtonText>Sub Menu 1</ButtonText>
  </Strings>
</Menu>
<Menu guid=" guidMyPackageCmdSet" id="MyToolBar1" priority="0x0010"
type="Toolbar">
</Menu>

And finally, the 'Groups' element organizes user's IDE command groups.

<Group guid="guidMyPackageCmdSet" id="MySubGroup1" priority="0x0020">
  <Parent guid="guidMyPackageCmdSet" id="MyGroup1" />
</Group>

To include vsct file into MSBuild-based VSPackage project, it is necessary to insert the following node used for calling VSCT compiler into your csproj project file (note, that in the auto-generated project created from an SDK template, a vsct file will be already included in a project):

<ItemGroup>
  <VSCTCompile Include="TopLevelMenu.vsct">
    <ResourceName>Menus.ctmenu</ResourceName>
  </VSCTCompile>
</ItemGroup>

In order to integrate a user-defined command or command group to one of the standard Visual Studio command groups, it is necessary to specify for your group the identifier of such standard group in the parent node. For example, top integrate your commands to a context menu of a project in the solution explorer window:

<Group guid="guidMyCmdSet" id="ProjectNodeContextMenuGroup" priority="0x07A0">
    <Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_PROJNODE" />
</Group>

As you can see, the standard IDM_VS_CTXT_PROJNODE is being used here. Following this link, you can discover a list of standard IDs for Visual Studio command groups.

Next, the ProvideMenuResource attribute of your Package-derived class should point to this node that you've inserted into your project earlier:

[ProvideMenuResource("Menus.ctmenu", 1)]
...
public sealed class MyPackage : Package

Assigning handlers to the commands defined in a VSCT file is possible through a service that is available through the IMenuCommandService. A reference for it can be obtained by the GetService method of your Package subclass:

OleMenuCommandService MCS = GetService(typeof(IMenuCommandService)) as
  OleMenuCommandService;

Let's examine an example in which we assign a handler to a menu command (this command should be declared in a vsct file beforehand):

EventHandler eh = new EventHandler(CMDHandler);
CommandID menuCommandID = new CommandID(guidCommand1CmdSet, id); 
//ID and GUID should be the same as in the VCST file
OleMenuCommand menuItem = new OleMenuCommand(eh, menuCommandID);
menuItem.ParametersDescription = "$";
MCS.AddCommand(menuItem);

To obtain command's arguments while handling its invocation, the EventArgs object should be casted into OleMenuCmdEventArgs:

void CMDHandler(object sender, EventArgs e)
{                 
  OleMenuCmdEventArgs eventArgs = (OleMenuCmdEventArgs)e;
                   if (eventArgs.InValue != null)
                       param = eventArgs.InValue.ToString();
  ...
}

Handling commands through EnvDTE.DTE interfaces.


The EnvDTE.DTE automation object allows for a direct manipulation (creation, modification and execution) of commands through the dte.Commands interface and dte.ExecuteCommand method. Utilizing the Automation Object Model for invoking, modifying and creating IDE commands, as opposed to using VSCT mechanism available only for VSPackage, allows the interaction with IDE commands from within Add-In extension packages as well.

The DTE automation object allows a direct creation, modification and invocation of commands through the DTE.Commands interface. A command can be directly added to the IDE by Commands.AddNamedCommand method (but only for an Add-In extension):

dte.Commands.AddNamedCommand(add_in, "MyCommand", "My Command", 
  "My Tooltip", true);

The command added in this way will be preserved by the IDE — it will reappear in the menu after IDE restart, even if the extension which created the command is not loaded itself. That's why this method should only be utilized during the first initialization of an Add-In module, after its installation (this is described in the section dedicated to Visual Studio Automation Object Model). The OnConnection method of an Add-In contains a special initialization mode which is invoked only for a single time in the module's entire lifetime. This method can be used to integrate UI elements into the IDE:

public void OnConnection(object application, 
                         ext_ConnectMode connectMode, 
                         object addInInst, ref Array custom)
{
  switch(connectMode)
  {
      case ext_ConnectMode.ext_cm_UISetup:
          ...
          break;

      ...
  }
}

The EnvDTE.Command interface represents a single IDE command. This interface can be used to modify a command which it references. It permits managing IDE commands from either a VSPackage, or an Add-In module. Let's obtain a reference to the EnvDTE.Command object for our custom command MyCommand1 and utilize this interface to assign a 'hot-key' to it for a quick access:

EnvDTE.Command MyCommand1 = 
MyPackage.DTE.Commands.Item("MyGroup.MyCommand1", -1);
MyCommand1.Bindings = new object[1] { "Global::Alt+1" };

The quick-access combination assigned to MyGroup.MyCommand1 will now be available through 'Keyboard, Environment' environment settings dialog.

As was mentioned before, Visual Studio command is not a UI element by itself. The Commands.AddCommandBar method allows the creation of such UI elements, as main menu items, toolbars, context menus and the association of these elements with user-created commands.

CommandBar MyToolbar = dte.Commands.AddCommandBar("MyToolbar1", 
  vsCommandBarType.vsCommandBarTypeToolbar) as CommandBar;
CommandBar MyMenu = dte.Commands.AddCommandBar("MyMenu1", 
  vsCommandBarType.vsCommandBarTypeMenu) as CommandBar;
CommandBarButton MyButton1 = MyCommand1.AddControl(MyToolbar) as 
  CommandBarButton;
MyButton1.Caption = "My Command 1";

The Delete method of Command/ CommandBar objects could be utilized to remove a command or toolbar from IDE.

MyCommand1.Delete();

In general, it is not recommended creating commands each time an Add-In plug-in is loaded and removing them each time it is un-loaded, as such behavior could slow down the initialization of IDE itself. Even more, in case the OnDisconnect method is somehow interrupted in the process, it is possible that the user commands will not be completely deleted from the IDE. That is why it is advised that the integration, and subsequent removal, of IDE commands should be handled at the times of module's installation/uninstallation, as for example, by obtaining DTE interface reference from a stand-alone installer application. The initialization of Add-In modules and acquisition of DTE references is thoroughly described in the article devoted to EnvDTE Automation Object Model.

Any IDE command (either custom or default one) could be called by the ExecuteCommand method. Here is the example of invoking our custom MyCommand1 command:

MyPackage.DTE.ExecuteCommand("MyGroup.MyCommand1", args);

To handle command execution, an Add-In extension should be derived from the IDTCommandTarget interface and it should also implement the Exec method:

public void Exec(string commandName, 
  vsCommandExecOption executeOption, ref object varIn, 
  ref object varOut, ref bool handled)
{
  handled = false;
  if(executeOption == 
    vsCommandExecOption.vsCommandExecOptionDoDefault)
  {
    if(commandName == "MyAddin1.Connect.MyCommand1")
    {
      ...
      handled = true;
      return;
    }
  }
}

References


  1. MSDN. Visual Studio Commands and Switches.
  2. MSDN. Visual Studio Command Table (.Vsct) Files.
  3. MSDN. Designing XML Command Table (.Vsct) Files.
  4. MSDN. Walkthrough: Adding a Toolbar to the IDE.
  5. MSDN. How VSPackages Add User Interface Elements to the IDE.
  6. MZ-Tools. HOWTO: Adding buttons, commandbars and toolbars to Visual Studio .NET from an add-in.
  7. MSDN. How to: Create Toolbars for Tool Windows.


Visual Studio tool windows


This item covers the extension of Visual Studio IDE through integration of a custom user tool window into the environment. Discussed are the issues of window registration and initialization in VSPackage and Add-In plug-in modules, hosting of user components and handling of window's events and states.

Introduction


Tool windows are child windows of Visual Studio MDI (Multiple Document Interface) and they are responsible for presenting various pieces of information to the user. Solution Explorer and Error List are the examples of tool windows. Usually tool windows' contents are not associated with any files and do not contain any editors, as separate document windows are reserved for such tasks.

For instance, PVS-Studio extension package integrates several tool windows into the IDE, with Output Window being the primary one. All other of its tool windows can be opened from this main window, as, for example, a search window for the grid. PVS-Studio Output Window itself can be opened from Visual Studio main menu (PVS-Studio -> Show PVS-Studio Output Window), but it also will be invoked automatically each time the analysis starts.

In most cases IDE creates and utilizes just a single instance for each one of its tool windows, and this instance will be preserved until IDE itself needs to shut down. Therefore, pressing the 'close' button on a tool window does actually hide it, and when this window is invoked for the second time, it becomes visible again, thus preserving any data that it contained before being 'closed'. But still, is it possible to crate Multi-Instance tool windows in the IDE, which are the windows that can exist in several instances at once. A tool window can also be associated with a certain UI context (as the so called dynamic window), and such window will be automatically displayed when the user enters this context.

Integration of a tool window into the IDE is supported by VSPackage and Add-In extensions (although the methods for it are different); it requires the specification of the window's initial settings and its registration in the system registry.

Registering and initializing user tool windows


A VSPackage project template that is installed together with Visual Studio SDK allows you to create a sample tool window in the extension project which this template generates. Such a project should already contain all of the basic components which will be described below, so it could be conveniently used as a sample for experimenting with Visual Studio tool window integration process for VSPackage plug-ins.

Registering, initializing and invoking a tool window in VSPackage

Registering a custom user window in the environment requires writing of the data that defines this window into a special section of Visual Studio registry hive. This process can be automated by generating a pkgdef file that can contain all of the required window registration information. The contents of this pkgdef file can be specified through special registration attributes of your Package subclass.

The immediate registration of a user-created tool window into VSPackage extension is handled by ProvideToolWindowattribute of Package subclass:

[ProvideToolWindow(typeof(MyWindowPane), Orientation = 
ToolWindowOrientation.Right, Style = VsDockStyle.Tabbed, Window = 
Microsoft.VisualStudio.Shell.Interop.ToolWindowGuids.Outputwindow, 
MultiInstances = false, Transient = true, Width = 500, Height = 250, 
PositionX = 300, PositionY = 300)]

Let's examine several parameters of this attribute. The Typeof parameter points to user implementation of the window's client area (a subclass of ToolWindowPane). The MultiInstances parameter enables the Multi-Instance mode for a window, in which multiple instances of the window can be opened simultaneously. The Orientation, Size and Style parameters specify the initial position of a window when it is opened for the first time by the user. It should be noted that the position specified by these parameters will only be used once, when a tool window is displayed for the first time; at all of the subsequent iterations of opening this window, the IDE will be restoring its screen position from the previous one, that is the position before a window was closed. The Transient parameter indicates whether the window will be automatically opened after Visual Studio environment is loaded in case it already have been opened during the previous session of the IDE.

It should also be remembered that the initialization of a user window by VSPackage (the initialization itself will be covered later) does not necessarily occur at the same moment as the initialization of a Package subclass for which we provided this registration attribute. For example, after implementing a tool window for PVS-Studio plug-in, we've encountered an issue in which our custom window was automatically opened (but not focused/displayed) and placed among other window tabs at the bottom of the main window, and it was done immediately after Visual Studio started up, even though we've passed the Transient=true parameter to the ProvideToolWindow attribute. Although the plug-in itself is always initialized at IDE start-up, the window had not been fully initialized until after a first call to it, which was evident by the corrupted icon on aforementioned tab.

A dynamic visibility context can be specified for a window by the ProvideToolWindowVisibilityattribute:

[ProvideToolWindowVisibility(typeof(MyWindowPane), 
/*UICONTEXT_SolutionExists*/"f1536ef8-92ec-443c-9ed7-fdadf150da82")]

In this example, the window is set to be automatically displayed when the user enters the "Solution Exists" UI context. Take a note that each one of user's tool window requires a separate attribute and a window's type should be passed as a first argument to it.

The FindToolWindow method of a Package subclass can be utilized to create and display a tool window from a VSPackage extension. This method returns a reference to the specified tool window object, creating it if necessary (for instance, in case a single-instance window is called for a first time). Following is the example of invoking a single-instance tool window:

private void ShowMyWindow(object sender, EventArgs e)
{
  ToolWindowPane MyWindow = this.FindToolWindow(typeof(MyToolWindow), 
    0, true);
  if ((null == MyWindow) || (null == MyWindow.Frame))
  {
    throw new NotSupportedException(Resources.CanNotCreateWindow);
  }
  IVsWindowFrame windowFrame = (IVsWindowFrame) MyWindow.Frame;
  ErrorHandler.ThrowOnFailure(windowFrame.Show());
}

In this example, the window will be created in case it is called for the first time, or the window will be made visible in case it had been created before and then hidden. The FindToolWindow's third argument of the bool type specifies whether a new instance of a window should be created if the method was unable to find an already existing one.

To create a Multi-Instance tool window, the CreateToolWindow method can be used. It allows the creation of a window with a pre-defined identifier. An example of invoking such window:

private void CreateMyWindow(object sender, EventArgs e)
{
  for (int i = 0; ; i++)
  {
    // Find existing windows.
    var currentWindow = 
      this.FindToolWindow(typeof(MyToolWindow), i, false);
    if (currentWindow == null)
    {
      // Create the window with the first free ID.
      var window = 
       (ToolWindowPane)this.CreateToolWindow(typeof(MyToolWindow), i);

      if ((null == window) || (null == window.Frame))
      {
        throw new 
          NotSupportedException(Resources.CanNotCreateWindow);
      }
      IVsWindowFrame windowFrame = (IVsWindowFrame)window.Frame;

      ErrorHandler.ThrowOnFailure(windowFrame.Show());
      break;
    }
  }
}

Note that in this example the FindToolWindow method receives 'false' value as its third argument, i.e. we are searching for an unoccupied index before initializing a new window instance.

As was mentioned above, the environment will preserve position of a window after it is closed. But if, for whatever reason, it is necessary to specify the size and position of a window, it could be achieved through the SetFramePos method of the IVsWindowFrame interface:

Guid gd = Guid.Empty;
windowFrame.SetFramePos(VSSETFRAMEPOS.SFP_fDockBottom, ref gd, 20, 20, 
  200, 200);

A call to the SetFramePos() should always be made only after the Show() method is executed.

Creating and invoking a window from Add-In extension

A user tool window can be initialized from an Add-In extension with the help of the EnvDTE Windows2 interface:

public void OnConnection(object application, 
ext_ConnectMode connectMode, object addInInst, ref Array custom)
{
  _applicationObject = (DTE2)application;
  _addInInstance = (AddIn)addInInst;
  EnvDTE80.Windows2 window;
  AddIn add_in;
  object ctlobj = null;
  Window myWindow;

  // Get the window object
  add_in = _applicationObject.AddIns.Item(1);
  window = (Windows2)_applicationObject.Windows;

  // This section specifies the path and class name of the windows 
  // control that you want to host in the new tool window, as well as
  // its caption and a unique GUID.
  string assemblypath = "C:\\MyToolwindow\\MyToolWindowControl.dll";
  string classname = " MyToolWindowControl.MyUserControl";
  string guidpos = "{E87F0FC8-5330-442C-AF56-4F42B5F1AD11}";
  string caption = "My Window";

  // Creates the new tool window and inserts the user control into it.
  myWindow = window.CreateToolWindow2(add_in, assemblypath, 
  classname, caption, guidpos, ref ctlobj);
  myWindow.Visible = true;
}

In the example above, a user tool window was created using the MyToolWindowControl.MyUserControl as a client area control. The MyToolWindowControl.MyUserControl class could either be located in the same assembly as the add-in that initializes it, or it could be provided by a stand-alone assembly with a full COM visibility (though the 'Register for COM Interop' option in project settings). The regular composite UserControl subclass could be utilized as MyUserControl.

Implementing a user toolwindow in a VSPackage module


Tool window consists of a frame border and a client area. A frame is provided by the environment and is responsible for performing docking with other interface objects of the environment, as well as for size and position of the window itself. A client area is a pane, controlled by a user, which houses the contents of a window. Tool windows can host user-created WinForms and WPF components and are capable of handling regular events, such as OnShow, OnMove, etc.

A user tool window, or its client area to be more precise, can be implemented by inheriting the class representing a standard empty IDE window — ToolWindowPane

[Guid("870ab1d8-b434-4e86-a479-e49b3c6797f0")]
public class MyToolWindow : ToolWindowPane
{

  public MyToolWindow():base(null)
  {
    this.Caption = Resources.ToolWindowTitle;
    this.BitmapResourceID = 301;
    this.BitmapIndex = 1;
    ...

  }
}

The Guid attribute is used to uniquely identify each custom user window. In case a plug-in module creates several windows of different types, each one of them should be identified by its own unique Guid. A ToolWIndowPane subclass can be subsequently modified and host user-controlled components.

Hosting user components

A base ToolWindowPane class implements an empty tool window of the environment. Inheriting from this class allows hosting user-created WinForms or WPF components.

Up until Visual Studio 2008 version, tool windows only provided a native support for WinForms user components, although it still was possible to host WPF components through the WPF Interoperability ElementHost object. Starting from Visual Studio 2010, tool windows themselves are based on WPF technology, although they still provide a backward compatibility for hosting of WinForms components.

To host a user-created WinForms component inside a user tool window, the Window property of the ToolWindowPane base class should be overridden:

public MyUserControl control;

public MyToolWindow():base(null)
{
  this.Caption = Resources.ToolWindowTitle;
  this.BitmapResourceID = 301;
  this.BitmapIndex = 1;
  this.control = new MyUserControl();
}

public override IWin32Window Window
{
  get { return (IWin32Window)control; }
}

In the example above, the MyUserControl object is a regular composite component of the System.Windows.Forms.UserControl type and it can host any other user component inside itself. UserControl can also host WPF components by using WPF ElementHost object.

Starting from Visual Studio 2010, WPF components can be hosted by tool windows natively. To do this, a reference to the WPF component should be passed to the Content property of a base class:

public MyToolWindow():base(null)
{
  this.Caption = Resources.ToolWindowTitle;
  this.BitmapResourceID = 301;
  this.BitmapIndex = 1;
  base.Content = new MyWPFUserControl();
}

Please note that using the two methods described above simultaneously is not possible. When a reference to WPF component is assigned to the base.Content property, an overridden Window property is ignored.

The main PVS-Studio 'Output' window of our extension plug-in hosts a virtual grid based on SourceGrid open-source project. This window provides an interface for handling the results of static analysis. The grid itself is bound to a regular ADO.NET table of the System.Data.Datatable type, which is utilized for storing analysis results. Until 4.00 version of PVS-Studio extension, it utilized a regular IDE 'Error List' window, but as the analyzer evolved, the capabilities of this default window became insufficient. Apart from being un-extendable with such specific static analysis UI elements as, for example, false positive suppression and filtering mechanisms, the Error List is itself basically a 'real' grid, as it stores all of the displayed elements inside itself. Therefore, this grid only permits an adequate handling of 1-2k messages at a time, performance wise, as a greater number of messages already can cause quite a noticeable lag to the environment's UI. On the other hand, our own practice of using static analysis on relatively large projects, such as Chromium or LLVM, demonstrated that a total number of diagnostic messages (taking into account all of the marked false alarms and low-lever user diagnostics as well) could easily reach tens of thousands or even more.

Therefore, by implementing a custom output window, based on virtual grid that is connected to a DB table, PVS-Studio is able to display and provide convenient handling for hundreds of thousands of diagnostic messages at once. Also, the ability for a convenient and flexible filtering of the analysis results is quite an important aspect of handling a static analyzer, as the manual examination even of only such a "tiny" amount of messages as 1-2k is nearly impossible for a single user. The storage of analysis results in a Datatable object by itself provides quite a convenient filtering mechanism based on a simple SQL queries, even more so because the results of such queries become visible immediately inside the bound virtual grid.

Handling tool windows events

A client area of a tool window (represented by our ToolWindowPane subclass) can process the regular events of user-interface interactions. The IVsWindowFrameNotify3 interface can be used for subscribing to window events. Let's provide an example of implementing this interface:

public sealed class WindowStatus: IVsWindowFrameNotify3
{
  // Private fields to keep track of the last known state
  private int x = 0;
  private int y = 0;
  private int width = 0;
  private int height = 0;
  private bool dockable = false;

  #region Public properties
            
  // Return the current horizontal position of the window
  public int X
  {
    get { return x; }
  }
            
  // Return the current vertical position of the window
  public int Y
  {
    get { return y; }
  }

  // Return the current width of the window
  public int Width
  {
    get { return width; }
  }

  // Return the current height of the window
  public int Height
  {
    get { return height; }
  }
            
  // Is the window dockable
  public bool IsDockable
  {
    get { return dockable; }
  }

  #endregion

  public WindowStatus()
  {}

  #region IVsWindowFrameNotify3 Members
  // This is called when the window is being closed
  public int OnClose(ref uint pgrfSaveOptions)
  {
    return Microsoft.VisualStudio.VSConstants.S_OK;
  }

  // This is called when a window "dock state" changes. 
  public int OnDockableChange(int fDockable, int x, int y, int w, 
  int h)
  {
    this.x = x;
    this.y = y;
    this.width = w;
    this.height = h;
    this.dockable = (fDockable != 0);
                
    return Microsoft.VisualStudio.VSConstants.S_OK;
  }

  // This is called when the window is moved
  public int OnMove(int x, int y, int w, int h)
  {
    this.x = x;
    this.y = y;
    this.width = w;
    this.height = h;

    return Microsoft.VisualStudio.VSConstants.S_OK;
  }

  // This is called when the window is shown or hidden
  public int OnShow(int fShow)
  {
    return Microsoft.VisualStudio.VSConstants.S_OK;
  }

  /// This is called when the window is resized
  public int OnSize(int x, int y, int w, int h)
  {
    this.x = x;
    this.y = y;
    this.width = w;
    this.height = h;
    return Microsoft.VisualStudio.VSConstants.S_OK;
  }

  #endregion

}

As evident by this sample code above, the WindowsStatus class implementing the interface is able to process such window state changes, as the alterations in window's size, position, visibility properties and so on. Now, let's subscribe our window for handling these events. It requires the OnToolWindowCreated method to be overridden in our ToolWindowPane subclass:

public class MyToolWindow: ToolWindowPane
{
  public override void OnToolWindowCreated()
  {
    base.OnToolWindowCreated();

    // Register to the window events
    WindowStatus windowFrameEventsHandler = new WindowStatus();

ErrorHandler.ThrowOnFailure(
  ((IVsWindowFrame)this.Frame).SetProperty(
  (int)__VSFPROPID.VSFPROPID_ViewHelper, 
  (IVsWindowFrameNotify3)windowFrameEventsHandler));
  }

  ...
}

Controlling window state

A window state can be controlled through event handlers of our IVsWindowFrameNotify3 implementation.

The OnShow method notifies the extension package about changes in tool window's visibility state, allowing to track the appearance of the window to a user, when, for example, user switches windows by clicking on window tabs. Current visibility state could be obtained by the fShow parameter, which corresponds to the __FRAMESHOW list.

The OnClose method notifies about the closure of a window frame, allowing to define IDE behavior in case of this event with the pgrfSaveOptions parameter, which controls the default document saving dialog (__FRAMECLOSE).

The OnDockableChange method informs the package on window's docking status changes. The fDockable parameter indicates whether a window is docked to another one; other parameters control window's size and position before and after the docking event.

The parameters of OnMove and OnSize methods provide window's coordinates and size while it is being dragged or resized.

References


  1. MSDN. Kinds of Windows.
  2. MSDN. Tool Windows.
  3. MSDN. Tool Window Essentials.
  4. MSDN. Tool Window Walkthroughs.
  5. MSDN. Arranging and Using Windows in Visual Studio.
  6. MZ-Tools. HOWTO: Understanding toolwindow states in Visual Studio.


Integrating into Visual Studio settings


This item covers the extension of Visual Studio by integrating into its 'Settings' dialog pages. Option page registration and integration into the IDE for different kinds of extension packages will be examined, as well as the means to display various standard and user-created components inside a custom settings page. Also covered are the ways of accessing environment settings through Visual Studio Automation model and preservation mechanism for option pages.

Introduction


Visual Studio employs a single unified dialog window to provide an access to the settings of its various components. This window is available through the IDE Tools -> Options main menu item. A basic element of Visual Studio settings is an Options Page. The Options dialog window arranges its pages in a tree-like structure according to the membership of the pages in their respective functional groups. Each one of these pages could be uniquely identified by the name of its group and its own name. For example, Visual Basic source code editor settings page is "Text Editor, Basic".

Extension packages are able to access and modify the values of various settings from option pages registered in the IDE. They can also create and register their own custom options pages in the environment through the automation object model and MPF classes (Managed Package Framework, available only to VSPackage extensions). Visual Studio contains an embedded mechanism for preserving the state of its settings objects; it is enabled by default, but can be overridden or disabled.

Creating and registering user options pages


It can be useful for a Visual Studio extension plug-in to be associated with one or several custom options pages from the Tools->Options dialog window. Such a tool for configuring an extension will conform to the environment's UI paradigm and is actually quite convenient for handling your extension's settings from within the IDE itself. The methods of implementing and integrating custom user options page into the IDE can vary, as they depend upon the type of the extension being developed and the technology being used (either an automation model or MPF).

Integrating settings through an MPF class

Managed Package Framework allows creating custom options pages by inheriting from the DialogPage. As the environment loads each of its options pages independently when accessing the corresponding section of the Tools->Options dialog, each page must be implemented with an independent object as a result.

The object which implements a custom page should be associated with your VSPackage through the ProvideOptionPage attribute of the corresponding Package subclass.

[ProvideOptionPageAttribute(typeof(OptionsPageRegistration),
"MyPackage", "MyOptionsPage", 113, 114, true)]

This attribute designates the names for the options page itself and for group that it belongs to, as it should be displayed in the IDE options dialog. A separate attribute should be used for every custom page that is to be integrated by the extension. In fact, this attribute is used to provide a registration for the page through pkgdef file and it does not directly affect the execution in any other way. For the user options page to be correctly rendered by the environment, the page should be registered in the following node of the system registry:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\<VsVersion>\
ToolsOptionsPages

Here <vsversion> is the version number of Visual Studio IDE, 10.0 for example. This record will be automatically created when ProvideOptionPage attribute is utilized. It should be noted that a correct uninstallation of an extension package also requires purging all of the records that this extension had written to the system registry before, including the ones belonging to its options pages. As the versions of Visual Studio IDE starting from 2010 can utilize VSIX packages to deploy/uninstall VSPackage plug-ins, the VSIX installer will automatically perform such registry operations according to its pkgdef file. But earlier versions of IDE may require manual registry cleaning, for instance by a stand-alone installer application.

The 6th bool-type argument of the attribute's constructor allows the user's custom options page to be registered as an automation object. This exposes the page to the Automation Object Model, providing an access to its settings through the EnvDTE interfaces for other third-party plug-ins. Registering an automation object requires the creation of several records in the system registry (it is performed automatically when using the aforementioned attributes) in the following nodes:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\<Version\Packages\
<PackageGUID>\Automation 

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\<Version>\
AutomationProperties

The ProvideProfile attribute allows registering an options page or any other independent object with the build-in mechanism for setting's state preservation, provided that such user object implements the IProfileManager interface.

Implementing an MPF DialogPage subclass

As a minimal requirement for DialogPage subclass to implement an IDE options page, this derived class should contain at least one public property. Here is an example of such a basic implementation:

namespace MyPackage
{
  class MyOptionsPage : DialogPage
  {
    bool myOption = true;
    public bool MyOption
    {
        get { return this. myOption; }
        set { this. myOption = value; }
    }
  }
}

To display such a generic implementation of DialogPage subclass, IDE will utilize a standard PropertyGrid control as a client area of the page window, which in turn will contain all public properties of this subclass. This could be convenient in case your extension's configuration properties are rather simple, so handling them through embedded PropertyGrid editors does not present any trouble. Using a control native to IDE will also exempt you from common issues with incorrect UI scaling for components on different DPI resolutions in the Visual Studio Options dialog.

However, if you want to host a user-created control inside an options page, it could be achieved by overriding the Window property of your DialogPage subclass:

[BrowsableAttribute(false)]
protected override IWin32Window Window
{
  get
  {
    return MyUserControl;
  }
}

A reference to the IWin32Window object implementing the window's client area should be returned by this property. Visual Studio required its options pages to be constant, i.e. they should not be recreated for any of their subsequent calls. As Windows Forms objects can delete and recreate their window handles at will, it is recommend passing a reference to the object derived from a UserControl type.

The AutomationObject property of a custom options page derived from the DialogPage class determines the public properties which are shown by the default display mechanism and persisted by the IDE. AutomationObject returns a reference to the DialogPage subclass itself by default, but if it returns a reference to some other object, then the properties of that returned object will be preserved and displayed instead. By default, system registry serves as a local storage for state preservation mechanism. Overriding the DialogPage.SaveSettingsToStorage method makes it possible to change the way of object's state preservation method (similar could be done to the state restoration through LoadSettingsFromStorage override).

public override void SaveSettingsToStorage() { ... }

Custom pages registered as automation objects can store their settings, along with settings from other options pages, in an external XML file through the standard IDE command Tools -> Import/Export Settings, by the default implementation of the SaveSettingsToXml method which also can be overridden if necessary.

Of course, integrating a page into Visual Studio settings dialog is not the exclusive or mandatory way of creating configuration interface for an IDE plug-in. If the capabilities of a regular PropertyGrid are insufficient and there are no future plans to utilize the embedded mechanism for settings preservation, then it could be quite reasonable to implement an IDE-independent settings dialog. The advantages of this approach are high portability (for instance, a plug-in that could be used with multiple IDEs) and complete control over the dialog window itself, which in turn substantially alleviates the support of various end-user configurations. On the downside, such solution makes your settings inaccessible to third-party developers through the automation object model.

For configuring its settings, PVS-Studio extension package utilizes a custom state preservation mechanism that operates through an external XML file, so that the options pages which the plug-in integrates into the IDE are provided only as means for displaying and modifying these internal settings. Initially, the embedded settings preservation functionality of Visual Studio created conflicts with PVS-Studio's own settings mechanism in the earlier versions of the plug-in, leading to setting de-synchronization issues. This demonstrated to us that even in the presence of an independent settings management inside the extension, it still may be necessary to override some of Visual Studio regular mechanisms (maybe even by an empty method).

Integrating settings through an Add-In XML definition

A user options page can be integrated into the IDE through an independent XML definition of an Add-In extension. The contents of such a user page should be implemented as a user component, for example as an System.Windows.Forms.UserControl. This component is not associated with an Add-In itself, thus it can be implemented either inside the extension's assembly or as an independent library altogether. An add-in XML file could even be created for such user component alone, without any definitions for an Add-In extension. Let's examine an XML definition for an Add-In module which also contains a definition for a user's custom options page.

<?xml version="1.0" encoding="UTF-16" standalone="no"?>
<Extensibility xmlns="http://schemas.microsoft.com/AutomationExtensibility">
  <HostApplication>
    <Name>Microsoft Visual Studio Macros</Name>
    <Version>10.0</Version>
  </HostApplication>
  <HostApplication>
    <Name>Microsoft Visual Studio</Name>
    <Version>10.0</Version>
  </HostApplication>
  <Addin>
    <FriendlyName>My Add in</FriendlyName>
    <Description>My Addin 1</Description>
    <Assembly>c:\MyAddIn1\MyAddin1.dll</Assembly>
    <FullClassName>MyAddin1.Connect</FullClassName>
    <LoadBehavior>0</LoadBehavior>
    <CommandPreload>1</CommandPreload>
    <CommandLineSafe>0</CommandLineSafe>
  </Addin>
  <ToolsOptionsPage>
    <Category Name="MyAddIn1">
      <SubCategory Name="My Tools Options Page">
      <Assembly> c:\MyAddIn1\MyAddin1.dll</Assembly>
      <FullClassName>MyAddin1.UserControl1</FullClassName>
      </SubCategory>
    </Category>
  </ToolsOptionsPage>
</Extensibility>

A description for custom options page is located inside the <toolsoptionspage> node. The <assembly> sub-node points to the library which contains a user component for the client area of the page. The <fullclassname> contains the full name of a user component in a Namespace.ClassName format. The <category> and <subcategory> nodes define position of a user page inside the Tools->Options tree-like structure by specifying page's group and personal names. Any existing group names, as well as a new one, can be used as a <category> value. As evident by the example, a user MyAddin1.UserControl1 component is located inside the same assembly as the add-in itself, though this is not a mandatory requirement.

Visual Studio loads a page after it is opened by a user for the first time through the Options dialog window. As opposed to the integration of a page through the Managed Package Framework, the description of a page is stored within an XML description addin file, so the page will be initialized only after the environment discovers such a file. Visual Studio reads addin files which are available to it immediately after start-up. The Environment -> Add-In/Macross Security options page specifies the paths which are used for addin discovery. Contrary to custom option pages implemented through inheriting the MPF classes, such high level approach to the integration does not register such a page as an automation object, and so it does not provide ways to access the page's contents through automation object model or to utilize the embedded state preservation mechanism of the environment.

Accessing option pages through the automation


Visual Studio Automation Object Model provides the means of accessing various system settings of the Tools->Options dialog, excluding some of the pages, such as 'Dynamic Help' and 'Fonts and Colors pages (they are available through separate APIs). User-created custom option pages are also available through the automation model in case they are registered as automation objects themselves (as described in the previous section).

The get_Properties method can be utilized to obtain the necessary settings:

Properties propertiesList = PVSStudio.DTE.get_Properties("MyPackage", 
"MyOptionsPage");

The option page can be identified by its own name and the name of group it belongs to. Here is the example of obtaining value for a specific property:

Property MyProp1 = propertiesList.Item("MyOption1");

The value of the property can be accessed and modified through the MyProp1.Value.

The ShowOptionPage method of the Package MPF subclass can be used to open and display the custom options page inside the Options window.

MyPackage.ShowOptionPage(typeof(MyOptionsPage));

As evident by the example, this method takes the type of a user-created DialogPage subclass. However, if it is required to open any other page which is not part or your extension project, a standard IDE page for example, then it could be located by its GUID identifier available at this registry branch:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\9.0\
ToolsOptionsPages\<OptionsPageNme>\

Here <optionspagename> is the name of a page inside the Tools -> Options dialog. Following is the example of opening a standard TextEditor -> GeneralIDE settings page through the IMenuCommandService global service:

string targetGUID = "734A5DE2-DEBA-11d0-A6D0-00C04FB67F6A";
var command = new CommandID(VSConstants.GUID_VSStandardCommandSet97,
  VSConstants.cmdidToolsOptions);
var mcs = GetService(typeof(IMenuCommandService)) 
  as MenuCommandService;
mcs.GlobalInvoke(command, targetGUID);

In fact, this code is equivalent to the execution of the Tools.Options IDE command. It can be invoked through the ExecuteCommand method of the EnvDTE.DTE:

dte.ExecuteCommand("Tools.Options", 
"734A5DE2-DEBA-11d0-A6D0-00C04FB67F6A").

References


  1. MSDN. Options Pages.
  2. MSDN. State Persistence and the Visual Studio IDE.
  3. MSDN. User Settings and Options.
  4. MSDN. Registering Custom Options Pages.
  5. MSDN. Providing Automation for VSPackages.


Visual Studio project model


This item covers the structure of Visual Studio project model and its implementation on the example of Visual C++ (VCProject). Also included are the cases of using the project model for enumeration of project elements and obtaining their compilation properties through the corresponding configurations. Isolated Shell based Atmel Studio environment will be examined as an example of a third-party implementation of the project model.

Introduction


Visual Studio project model is a collection of interfaces describing the properties of a compiler, linker and other build tools, as well as the structure of MSVS-compatible projects themselves, and it is connected with the Visual Studio Automation Object Model through the VCProjects late-bound properties. Visual C++ project model extends the standard Visual Studio project model, providing access to the specific functionality of Visual C++ (vcproj/vcxproj) project types. Visual C++ project model is a stand-alone COM component available through the VCProjectEngine.dll assembly, which could also be used independently outside of Visual Studio development environment. It is possible for third-party developers to create custom implementations of the project model, adding the support of new languages and compilers into Visual Studio.

Project model structure


Visual Studio provides an extendable project-neutral object model that represents solutions, projects, code objects, documents, etc. Every MSVS project type has a corresponding project automation interface. Every tool in the environment that has a project also has an object of the 'Project' type associated with it. Standard Visual C++ project model also complies with this general automation project model scheme:

Projects
  |- Project -- Object(unique for the project type)
      |- ProjectItems (a collection of ProjectItem)
          |- ProjectItem (single object) -- ProjectItems (another
                                                          collection)
              |- Object(unique for the project type)

The 'Projects' interface provides an ensemble of abstract objects of the 'Project' type. The 'Project' interface defines an abstract project, i.e. it can reference an object from any project model that complies with the standard scheme. Any peculiar properties of a specific model should be defined through a special interface which is unique only to this model alone. A reference for such an object could be acquired through the Project.Object property. For instance, specific properties of Visual C++ project could be obtained through the VCProject interface, and for the Atmel Studio model it will be the AvrGCCNode interface:

Project proj;
...
VCProject vcproj = proj.Object as VCProject;
AvrGCCNode AvrGccProject = proj.Object as AvrGCCNode;

It is possible to obtain a list of all projects loaded in IDE and belonging to any project model type through the dte.Solution.Projects field; projects belonging to a particular model can be acquired through the DTE.GetObject method (see the example below for Visual C++ model):

Projects vcprojs = m_dte.GetObject("VCProjects") as Projects;

To obtain projects of all types, the following code could be used:

Projects AllProjs = PVSStudio.DTE.Solution.Projects;

The ProjectItems interface represents an ensemble of abstract solution tree elements of ProjectItem type. Similar to the Project interface, the ProjectItem can define any kind of element; it can even contain the same ProjectItems collection inside itself (accessible through the ProjectItem.ProjectItems) or it can be a Project altogether. An object unique for a specific project model can be obtained through the ProjectItem.Object field. For instance, a Visual C++ source code file is represented by a VCFile type, and Atmel Studio source file be the AvrGccFileNode interface:

ProjectItem projectItem;
...
VCFile file = projectItem.Object as VCFile;
AvrGccFileNode file = projectItem.Object as AvrGccFileNode;

An embedded project can be obtained in a similar manner when such an element of the hierarchy represents a project:

Project proj = projectItem.Object as Project;

Recursively walking all elements of a Solution tree's branch


The interface for controlling hierarchies IVsHierarchy can be used to perform a passing of Solution tree's branch. This interface provides an access to abstract nodes of a tree, each one of which in turn could be a leaf, a container of elements or a link to another hierarchy. Each tree node is uniquely identified through the DWORD identifier VSITEMID. Such identifiers are unique within the scope of a single hierarchy and possess a limited lifetime within it.

A hierarchy object can be obtained for a tree branch of a single project through the VsShellUtilities.GetHierarchy method:

public static IVsHierarchy ToHierarchy(EnvDTE.Project project)
{
  System.IServiceProvider serviceProvider = 
    new ServiceProvider(project.DTE as
  Microsoft.VisualStudio.OLE.Interop.IServiceProvider);
  Guid guid = GetProjectGuid(serviceProvider, project);
  if (guid == Guid.Empty)
    return null;
  return VsShellUtilities.GetHierarchy(serviceProvider, guid);
}

In the example above, the hierarchy was obtained for a project through its GUID identifier. Consider the example of obtaining this GUID identifier for a project:

private static Guid GetProjectGuid(System.IServiceProvider 
  serviceProvider, Project project)
{
  if (ProjectUnloaded(project))
    return Guid.Empty;

  IVsSolution solution = 
   (IVsSolution)serviceProvider.GetService(typeof(SVsSolution)) as
     IVsSolution;
  IVsHierarchy hierarchy;
  solution.GetProjectOfUniqueName(project.FullName, out hierarchy);
  if (hierarchy != null)
  {
    Guid projectGuid;

    ErrorHandler.ThrowOnFailure(
      hierarchy.GetGuidProperty(
      VSConstants.VSITEMID_ROOT,
    (int)__VSHPROPID.VSHPROPID_ProjectIDGuid,
      out projectGuid));

    if (projectGuid != null)
    {
      return projectGuid;
    }
  }

  return Guid.Empty;
}

The IEnumHierarchies interface permits obtaining all of the hierarchies for projects of a particular type through the solution. GetProjectEnum method. Here is an example of obtaining the hierarchies for every Visual C++ project in a solution tree:

IVsSolution solution = PVSStudio._IVsSolution;
if (null != solution)
{
  IEnumHierarchies penum;
  Guid nullGuid = Guid.Empty;
  Guid vsppProjectGuid = 
    new Guid("8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942");

  //You can ask the solution to enumerate projects based on the
  //__VSENUMPROJFLAGS flags passed in. For
  //example if you want to only enumerate C# projects use
  //EPF_MATCHTYPE and pass C# project guid. See
  //Common\IDL\vsshell.idl for more details.
  int hr = solution.GetProjectEnum(
    (uint)(__VSENUMPROJFLAGS.EPF_LOADEDINSOLUTION |   
    __VSENUMPROJFLAGS.EPF_MATCHTYPE),
    ref vsppProjectGuid, out penum);
  ErrorHandler.ThrowOnFailure(hr);
  if ((VSConstants.S_OK == hr) && (penum != null))
  {
    uint fetched;
    IVsHierarchy[] rgelt = new IVsHierarchy[1];
    PatternsForActiveConfigurations.Clear();
    while (penum.Next(1, rgelt, out fetched) == 0 && fetched == 1)
    {
      ...
    }
  }
}

As evident by the example above, the GetProjectEnum method provides hierarchies for projects based on a project kind specified by the GUID identifier. GUID identifiers for regular Visual Studio/MSBuild project types can be obtained here. The penum.Next() method allows us to enumerate all project hierarchies we've acquired (the rgelt array). It should be remembered that user-created project models could possess their own unique identifiers in case they define a new project type for themselves. Such custom user identifiers can be obtained from XML project files of the corresponding models.

But our own experience in developing PVS-Studio IDE plug-in demonstrates that an opposite situation is quite possible as well, that is, when a user-created project type uses a GUID from one of the stock project types, usually the one from which it was derived. In particular, we've encountered a VCProject type that was extended to provide development for Android platform. As a result, this project model extension had caused crashes in our plug-in because it did not provide several properties which are otherwise present in VCProject model (OpenMP for example) through the automation API. An intricacy of this situation is that such an extended project model type cannot be differentiated from a regular one, and thus, it is quite hard to correctly process it as well. Therefore, when you are extending a project model through your custom types, to avoid such conflicts with various IDE components (including other third-party extensions as well), it is always important to remember the necessity of providing means to uniquely identify your types.

Possessing an IVsHierarchy for the project, we are able to recursively enumerate all the elements of such solution tree branch through the hierarchy.GetProperty method, which in turn provides us with the specified properties for each one of the hierarchy nodes:

EnumHierarchyItemsFlat(VSConstants.VSITEMID_ROOT, MyProjectHierarchy, 
0, true);
  ...
public void EnumHierarchyItemsFlat(uint itemid, IVsHierarchy 
  hierarchy, int recursionLevel, bool visibleNodesOnly)
{
  if (hierarchy == null)
  return;
  int hr; object pVar;

  hr = hierarchy.GetProperty(itemid, 
    (int)__VSHPROPID.VSHPROPID_ExtObject, out pVar);

  ProjectItem projectItem = pVar as ProjectItem;
  if (projectItem != null)
  {
    ...
  }

  recursionLevel++;
  //Get the first child node of the current hierarchy being walked
  hr = hierarchy.GetProperty(itemid,
    (visibleNodesOnly ? (int)__VSHPROPID.VSHPROPID_FirstVisibleChild 
    :(int)__VSHPROPID.VSHPROPID_FirstChild),
    out pVar);
  Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(hr);
  if (VSConstants.S_OK == hr)
    {
      //We are using Depth first search so at each level we recurse
      //to check if the node has any children
      // and then look for siblings.
      uint childId = GetItemId(pVar);
      while (childId != VSConstants.VSITEMID_NIL)
      {
        EnumHierarchyItemsFlat(childId, hierarchy, recursionLevel, 
        visibleNodesOnly);
        hr = hierarchy.GetProperty(childId,
          (visibleNodesOnly ?
          (int)__VSHPROPID.VSHPROPID_NextVisibleSibling :
          (int)__VSHPROPID.VSHPROPID_NextSibling),
          out pVar);
        if (VSConstants.S_OK == hr)
        {
          childId = GetItemId(pVar);
        }
        else
        {
          Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(hr);
          break;
        }
      }
    }
  }

  private uint GetItemId(object pvar)
  {
    if (pvar == null) return VSConstants.VSITEMID_NIL;
    if (pvar is int) return (uint)(int)pvar;
    if (pvar is uint) return (uint)pvar;
    if (pvar is short) return (uint)(short)pvar;
    if (pvar is ushort) return (uint)(ushort)pvar;
    if (pvar is long) return (uint)(long)pvar;
    return VSConstants.VSITEMID_NIL;
  }

A ProjectItem object that we've acquired for each one of the tree's nodes will allow us to obtain its corresponding Visual C++ object (as well as the object for any other custom project model) through the Object filed, as was described earlier.

Enumerating all projects in solution tree

DTE.Solution.Projects interface can be used to enumerate all projects in the solution:

if (m_DTE.Solution.Projects != null)
  {
  try
    {
      foreach (object prj in m_DTE.Solution.Projects)
      {
        EnvDTE.Project proj = prj as EnvDTE.Project;
        if (proj != null)
          WalkSolutionFolders(proj);
      } 
    }
  }

Besides projects, Solution tree can also contain folder nodes (Solution Folders). They should also be taken into account while processing each Project element:

public void WalkSolutionFolders(Project prj)
{
  VCProject vcprj = prj.Object as VCProject;
  if (vcprj != null && prj.Kind.Equals(VCCProjectTypeGUID))
  {
    if (!ProjectExcludedFromBuild(prj))
    {
      IVsHierarchy projectHierarchy = ToHierarchy(prj);
      EnumHierarchyItemsFlat(VSConstants.VSITEMID_ROOT, 
      projectHierarchy, 0, false);
    }
  }
  else if (prj.ProjectItems != null)
  {
    foreach (ProjectItem item in prj.ProjectItems)
    {
      Project nextlevelprj = item.Object as Project;
      if (nextlevelprj != null && !ProjectUnloaded(nextlevelprj))
      WalkSolutionFolders(nextlevelprj);
    }
  }
}

Projects that are excluded from the build should be inspected separately, as they are not accessible through the automation model after being unloaded from the IDE:

public bool ProjectExcludedFromBuild(Project project)
{
  if (project.UniqueName.Equals("<MiscFiles>", 
    StringComparison.InvariantCultureIgnoreCase))
  return true;
  Solution2 solution = m_DTE.Solution as Solution2;
  SolutionBuild2 solutionBuild = 
    (SolutionBuild2)solution.SolutionBuild;
    SolutionContexts projectContexts = 
    solutionBuild.ActiveConfiguration.SolutionContexts;
    //Skip this  project if it is excluded from build.
    bool shouldbuild = 
      projectContexts.Item(project.UniqueName).ShouldBuild;
    return !shouldbuild;
}

Enumerating selected elements

The DTE.SelectedItems interface can be used to enumerate solution elements which are selected in the Solution Explorer window.

foreach (SelectedItem item in items)
{
  VCProject vcproj = null;
  if (item.Project != null)
  {
    vcproj = item.Project.Object as VCProject;

    if (vcproj != null && item.Project.Kind.Equals("{" + 
      VSProjectTypes.VCpp + "}"))
      {
        IVsHierarchy projectHierarchy = ToHierarchy(item.Project);
        PatternsForActiveConfigurations.Clear();
        EnumHierarchyItemsFlat(VSConstants.VSITEMID_ROOT, 
        projectHierarchy, 0, false, files, showProgressDialog);
      }
      else if (item.Project.ProjectItems != null)
      {
        //solution folder
        if (!ProjectUnloaded(item.Project))
          WalkSolutionFolders(item.Project);
      }
    }
    else if (item.ProjectItem != null)
    {
      //walking files
      ...
      else if (item.ProjectItem.ProjectItems != null)
      if (item.ProjectItem.ProjectItems.Count > 0)
        WalkProjectItemTree(item.ProjectItem);
    }
  }
  
private void WalkProjectItemTree(object CurrentItem)
{
  Project CurProject = null;
  CurProject = CurrentItem as Project;
  if (CurProject != null)
  {
    IVsHierarchy projectHierarchy = ToHierarchy(CurProject);
    PatternsForActiveConfigurations.Clear();
    EnumHierarchyItemsFlat(VSConstants.VSITEMID_ROOT, 
      projectHierarchy, 0, false);

    return;
  }
    ProjectItem item = null;
    item = CurrentItem as ProjectItem;
    if (item != null)
    {
        ...
        if (item.ProjectItems != null)
            if (item.ProjectItems.Count > 0)
            {
                foreach (object NextItem in item.ProjectItems)
                    WalkProjectItemTree(NextItem);
            }
    }
}

Visual C++ project model. Configurations and properties of projects and files


To this moment, we have been examining a general, exterior part of Visual Studio project model, declared mostly in EnvDTE namespace, which is available from any version of project model implementation. Now, let's talk about one of the regular implementations of this model: Microsoft Visual C++. It is declared in the VisualStudio.VCProjectEngine namespace.

Visual C++ project model implements a general Visual Studio project model, so interfaces described in the previous chapters can be utilized for projects of this particular model as well. Interfaces that are specific to Visual C++ model are defined inside the Microsoft.VisualStudio.VCProjectEngine.dll assembly. This assembly should be added to the references of the extension that is being developed.

Visual C++ physically stores build configurations (compilation and linking parameters, pre-build and post-build steps, external tool command lines, etc.) for C/C++ source files inside its XML-based project files (vcproj/vcxproj). These settings are available to Visual Studio users through property page dialogs.

Each combination of project's build configuration (Debug, Release, etc.) and platform (Win32, IA64, x64, etc.) is associated with a separate collection of settings. Although majority of the settings are defined at the project level, it is possible to redefine separate properties for each individual file (file properties are inherited from its project by default). The list of properties which can be redefined at the file level is dependent upon the type of a file in question. For example, only the ExcludedFromBuild property can be redefined for header files, but cpp source files permit the redefinition for any of its compilation properties.

Obtaining configurations

Visual C++ project model presents property pages through the VCConfiguration (for a project) and VCFileConfiguration (for a file) interfaces. To obtain these objects we will start from a ProjectItem object which represents an abstract Solution tree element.

ProjectItem item;
VCFile vcfile = item.Object as VCFile;
Project project = item.ContainingProject;
String pattern = "Release|x64";
if (String.IsNullOrEmpty(pattern))
  return null;

VCFileConfiguration fileconfig = null;
IVCCollection fileCfgs = (IVCCollection)vcfile.FileConfigurations;
fileconfig = fileCfgs.Item(pattern) as VCFileConfiguration;
if (fileconfig == null)
  if (fileCfgs.Count == 1)
    fileconfig = (VCFileConfiguration)fileCfgs.Item(0);

In the example above we've acquired a file configuration for VCFile object (which represents a C/C++ header or a source file) by passing a configuration pattern (configuration's name and platform) to the Item() method. Build configuration pattern is defined on the project level. The following example demonstrates the acquisition of active configuration (the one that is selected in IDE) of a project.

ConfigurationManager cm = project.ConfigurationManager;
Configuration conf = cm.ActiveConfiguration;
String platformName = conf.PlatformName;
String configName = conf.ConfigurationName;
String pattern = configName + "|" + platformName;
return pattern;

The ActiveConfiguration interface should be handled with care. Quite often we've encountered exceptions when calling it from our PVS-Studio IDE extension package. In particular, this field sometimes becomes inaccessible through the automation object model when a user is building a project, or in the presence of any other heavy user interaction with Visual Studio UI. As there is no assured way of predicting such user actions, it is advised to provide additional error handlers for such 'bottlenecks' when accessing settings with automation model. It should be noted that this particular situation is not related to COM exception handling that was described in the previous article dedicated to EnvDTE interfaces, and it is probably related to some internal issues within the automation model itself.

Next, let's acquire the configuration for a project that contains the file in question:

VCConfiguration cfg=(VCConfiguration)fileconfig.ProjectConfiguration;

While the interfaces representing configurations themselves contain settings only from the 'General' tab of the property pages, references for individual build tools can be acquired through the VCConfiguration.Tools and VCFileConfiguration.Tool interfaces (note that a single file contains settings respectively for only one build tool). Let's examine the VCCLCompilerTool interface representing the C++ compiler:

ct = ((IVCCollection)cfg.Tools).Item("VCCLCompilerTool") as 
  VCCLCompilerTool;
ctf = fileconfig.Tool as VCCLCompilerTool;

Now let's acquire the contents of, for example, the AdditionalOptions field belonging to the compiler tool, using the Evaluate method to process any macros that we can encounter within its value:

String ct_add = fileconfig.Evaluate(ct.AdditionalOptions);
String ctf_add = fileconfig.Evaluate(ctf.AdditionalOptions);

Property Sheets

Property sheets are XML files with a *.props extension. They allow an independent definition of project's build properties, i.e. the command line parameters for various building tools, such as a compiler or a linker. Property sheets also support inheritance and can be used for specifying build configurations for several projects at once, i.e. the configuration defined inside the project file itself (vcproj/vcxproj) could inherit some of its properties from single or multiple props files.

To handle property sheets, Visual C++ project model provides the VCPropertySheet interface. A collection of VCPropertySheet objects can be obtained through the VCConfiguration.PropertySheets field:

IVCCollection PSheets_all = fileconfig.PropertySheets;

Similarly, the PropertySheets field of the VCPropertySheet interface provides a reference to a collection of child property sheet files for this object. Let's examine the recursive enumeration of all of the project's property sheets:

private void ProcessAllPropertySheets(VCConfiguration cfg, IVCCollection PSheets)
{
  foreach (VCPropertySheet propertySheet in PSheets)
  {
    VCCLCompilerTool ctPS = 
      (VCCLCompilerTool)((IVCCollection)propertySheet.Tools).Item(
      "VCCLCompilerTool");

  if (ctPS != null)
  {
    ...
            
    IVCCollection InherPSS = propertySheet.PropertySheets;
    if (InherPSS != null)
      if (InherPSS.Count != 0)
        ProcessAllPropertySheets(cfg, InherPSS);
      }
    }
}

In the example above we've obtained an object of VCCLCompilerTool type (that is compilation settings) for PropertySheet on every level. In this way we could gather all compilation parameters defined in every property sheet, including the embedded ones.

The VCPropertySheet interface does not contain means to evaluate macros within its fields, so as a work-around, the Evaluate method from the project's configuration can be used instead. But, such approach could also lead to the incorrect behavior in case the value of the macro being evaluated is related to the props file itself. For instance, several MSBuild macros which were introduced in the MSBuild version 4 could also be utilized inside vcxproj projects from Visual Studio 2010. Let's take the MSBuildThisFileDirectory macro that evaluates as a path to the directory containing file in which it is used. Now, evaluating this macro through the cfg.Evaluate will result in a path to the vcxproj file, and not to props file, which actually does contains this macro.

All of the property sheets in Visual C++ project can be divided between user and system files. By user files we understand the props files which were created and added to the project by a user himself. But even an empty template-generated MSVC project often includes several property sheets by default. These system props files are utilized by the environment to specify various compilation parameters which were set inside the project's property page interface by the user. For example, setting up the CharacterSet property to use Unicode manually in the Property Page interface will result in the appearance of a special property sheet in the 'Property Sheets' window which will define several preprocessor symbols (Unicode, _Unicode), and this properties subsequently will be inherited by the project. Therefore when processing properties from inside a Property sheet, one should always remember that compilation symbols defined in system props files are also returned by their corresponding property in the project's configuration through the automation API. Evidently, processing these two simultaneously while gathering compilation arguments can result in a duplication of such arguments.

Atmel Studio project model. Compilation settings in project toolsets.


We have examined an implementation of Visual Studio project model for C/C++ projects from Microsoft Visual C++, the model that is included into Visual Studio distribution by default. But Visual Studio automation model can be extended by adding interfaces for interacting with other custom project models from third-party developers (such custom project model can actually be implemented as a VSPackage extension). Therefore, if a developer of custom project model does provide interfaces for it, when it will be possible to interact with it the same way that we've interacted with Visual Studio regular models, such as Visual C++ one, as was described earlier.

For example, let's examine the interfaces of the project model that is provided by Atmel Studio IDE, the environment intended for development of embedded solutions. Now, you could probably ask – how does the topic of this article concern Atmel Studio, and we were always examining the interactions with Visual Studio? But, the truth is - Atmel Studio itself is the Visual Studio isolated shell application. We will not be discussing the essence of isolated shells here, and I will only mention that it is possible to develop the same kinds of extensions and plugins for isolated shells, as it is possible for the regular Visual Studio versions. You can familiarize yourself with some of the specifics regarding the development of Visual Studio plug-ins, including the isolated shell applications, in the previous chapter.

Now, Atmel project model itself is the implementation of a standard Visual Studio project model. And, exactly as for the Visual C++ projects, the common interfaces could be utilized with Atmel Studio projects as well. The interfaces that are specific for this model are declared in the AvrGCC.dll, AvrProjectManagement.dll and Atmel.Studio.Toolchain.Interfaces.dll files. These files can be obtained by downloading the Atmel Studio Extension Developer's Kit (XDK).

Atmel Studio project model physically stores its build parameters in cproj project files which themselves at the same time serve as project files for MSBuild system (as all of the standard project types in Visual Studio). Atmel project model supports C/C++ languages and utilizes special editions of GCC compilers for the purpose of building its source files.

Types of projects and toolchains

Atmel Studio provides 2 types of projects: C and C++ projects. Please note that these project types possess different GUIDs, so this should be taken into account when traversing the project tree.

Atmel project model also provides 2 compile tool chains – GNU C compiler and GNU C++ Compiler, each one possessing a separate set of options. It should be noted that while C projects can hold only C compiler options, the toolchain of C++ project consists of both C and C++ options sets. The appropriate compile options set will be selected by the build system automatically during compilation according to the extension of the source file being built. This means that in the case of a "mixed" project, 2 compile tool sets will be utilized at the same time!

The available option sets for each individual project can be obtained through the ProjectToolchainOptions interface.

ProjectItem item;
...
AvrGccFileNode file = item.Object as AvrGccFileNode;
AvrGCCNode project = file.ProjectMgr as AvrGCCNode;
AvrProjectConfigProperties ActiveProps = project.ConfigurationManager.GetActiveConfigProperties();
ProjectToolchainOptions ToolChainOptions = ActiveProps.ToolchainOptions;
if (ToolChainOptions.CppCompiler != null)
    //Toolchain compiler options for C++ compiler
if (ToolChainOptions.CCompiler != null)
    //Toolchain compiler options for C Compiler

Obtaining compilation settings

To extract individual compilation settings themselves, the CompilerOptions object that we've obtained earlier (a common base type for CppCompilerOptions and CCompilerOptions) can be utilized. Some of the settings could be taken directly from this object, such as Include paths of a project:

CompilerOptions options;
...
List<String> Includes = options. IncludePaths;

Please note that a part of the settings are shared between all project types (i.e. between the C and C++ ones). An example of such common settings is the one holding system includes:

List<String> SystemIncludes = options. DefaultIncludePaths;

But most of other settings are available only through the OtherProperties property of the Dictionary<string, ilist<string="IList<String">> type. As evident by the type of this property, each setting (a key) corresponds to a list of one or more values.

However, if you wish to obtain a whole command line passed to MSBuild from the project (and to the compiler thereafter), and not the individual settings values, such command line can be immediately obtained through the CommandLine property (which is a lot easier than in the case of VCProjectEngine!):

String RawCommandLine = this.compilerOptions.CommandLine;

It should be noted that General settings, such as system includes, still will not be present in a command line obtained in this way.

Also worth noting is that either individual setting values or the whole command line obtained in this way could still contain a number of unexpanded MSBuild macros. The GetAllProjectProperties method of the AvrGCCNode interface can be utilized to resolve such macros:

AvrGCCNode project;
...
Dictionary<string, string> MSBuildProps = new Dictionary<string, string>();
project.GetAllProjectProperties().ForEach(x =>MSBuildProps.Add(x.Key, x.Value));

Now we can replace each macro we encounter with the corresponding values from MSBuildProps collection.

Each file in the project can possess additional build parameters, additional compiler flags to be more precise. We can obtain such flags through the AvrFileNodeProperties interface:

AvrGccFileNode file;
...
AvrFileNodeProperties FileProps =  file.NodeProperties as AvrFileNodeProperties;
String AdditionalFlags = FileProps.CustomCompilationSetting;

References


  1. MSDN. Visual C++ Project Model.
  2. MSDN. Project Modeling.
  3. MSDN. Automation Model Overview.

Parallel Algorithm Framework

$
0
0

Keep it simple


I've been trying to implement a small framework to execute parallel algorithms in my engine. I decided to begin with the frustum culling and for the last week I tried to code a thread pool with mutex and load balancing. This was a huge mistake as it's too complex, the load balancing is poor and developing a decent job-stealing scheduler is pretty hard. This idea came with the concept of a global queue, and a local queue per thread.

There is a simple pattern you should always follow: KISS (Keep it simple...)

A lock-free queue with no mutex is much more efficient. There is no need to implement a job-stealing scheduler, it fits very well thin grained parallel algorithms with no dependencies between threads and it comes out of the box.

Light CPU Jobs


Atomic operation algorithms vs mutex


Posix and Windows mutex are efficient for threads with a lot of dependencies, but they have some drawbacks:
  • Force kernel/user space context switches and the delay is quite high, specially on Windows. This makes mutex a bad option for thin parallel algorithms, i.e. realtime applications and a game is a soft realtime application.
  • For the same reason mutexes don't scale well, atomic operation algorithms will keep an active waiting on the very same thread improving the performance
On the other hand algorithms based on atomic operations are difficult to debug and depend a lot on the architecture rather than on the OS, making development difficult, but x86 has some memory coherency, therefore if you are targeting this platform atomic operation algorithms will serve well in your engine.

There are almost two types of atomic operation algorithms:

  1. User space blocking algorithms
  2. User space lock-free algorithms

A lock-free queue for 32 and 64 bit systems


A lock-free queue will be the core of the synchronization in our engine, you will find some implementations out there but these are a little bit messy. Mine has two extra members: done and busy. These members will allow you to sync all the threads without having to add a single mutex.

//-----------------------------------------------------------------------
// momo (2012)
//-----------------------------------------------------------------------
// file: taskqueue.h
// author: Victor Egea Hernando (egea.hernando@gmail.com)
// description: lock-free queue based on
//     Simple, Fast, and Practical Non-Blocking and Blocking
//     Concurrent Queue Algorithms
//     Maged M. Michael Michael L. Scott
//     Department of Computer Science
//     University of Rochester
//-----------------------------------------------------------------------

#ifndef TASKQUEUE_H
#define TASKQUEUE_H

//-----------------------------------------------------------------------

#include <core/kernel/platform/atomic.h>
#include <core/kernel/debug/debug.h>

//-----------------------------------------------------------------------
// TaskQueue: lock-free queue
//-----------------------------------------------------------------------

template <class T>
class TaskQueue
{

    NO_COPY(TaskQueue)
    
protected:
    
    //-------------------------------------------------------------------
    // structures
    //-------------------------------------------------------------------
        
    typedef uint64 Ptr;
    
    //-------------------------------------------------------------------
    
    struct Node
    {        
        Ptr next;
   
        T value;
    };
    
    //-------------------------------------------------------------------
    // Pointer operations
    //-------------------------------------------------------------------

    static const uint64 PtrMask = 0x003fffffffffffff;
    static const uint64 CntMask = 0xffc0000000000000;

    //-------------------------------------------------------------------
    
    bool equal(const Ptr& ptr1, const Ptr& ptr2)
    {
        return ptr1 == ptr2;
    }
    
    //-------------------------------------------------------------------
    
    void set(Ptr& ptr, Node* node, uint32 count)
    {
        setNode(ptr, node);
        setCount(ptr, count);
    }
    
    //-------------------------------------------------------------------
    
    Ptr gen(Node* node, uint32 count)
    {
        Ptr ptr;
        
        setNode(ptr, node);
        setCount(ptr, count);
        
        return ptr;
    }
    
    //-------------------------------------------------------------------
    
    void setNode(Ptr& ptr, Node* node)
    {
        if ((uint64)node & CntMask) {
            dCritical(Format("pointer too long: %1").arg(Format::pointer(node)));
        }
        
        ptr = (Ptr)((uint64)ptr & CntMask | ((uint64)node & PtrMask));
    }
    
    void setCount(Ptr& ptr, uint32 counter)
    {
        if (counter >= (1 << 10)) {
            counter = counter % (1 << 10);
        }
        
        ptr = (Ptr)(((uint64)counter << 54) | ((uint64)ptr & PtrMask));
    }
    
    //-------------------------------------------------------------------
    
    Node* pointer(Ptr ptr)
    {
        return (Node*)((uint64)ptr & PtrMask);
    }
    
    uint32 count(Ptr ptr)
    {        
        return (uint64)ptr >> 54;
    }
    
public:
    
    TaskQueue()
    {
        Node* node = new Node;
        
        set(node -> next, null, 0);
        set(_head, node, 0);
        set(_tail, node, 0);
        
        _pops = 0;
    }
    
    //-------------------------------------------------------------------
    
    virtual ~TaskQueue()
    {
        delete pointer(_head);
    }
    
    //-------------------------------------------------------------------
    // safe & free-lock operations
    //-------------------------------------------------------------------
    
    void push(const T& value)
    {
        Node* node = new Node;
        
        node -> value = value;
        
        set(node -> next, null, 0);
        
        Ptr tail = null;
        
        //---------------------------------------------------------------
        
        FOREVER {
            tail = _tail;
                
            Ptr next = null;
            
            if (pointer(tail) != null) {
                next = pointer(tail) -> next;
            }
            
            if (equal(tail, _tail)) {
                if (pointer(next) == null) {
                    Ptr ptr = gen(node, count(next) + 1);
                    
                    Ptr res = atomic::cas64(pointer(tail) -> next, next, ptr);
                    
                    if (equal(res, next)) {
                        break;
                    }
                }
                else {
                    Ptr ptr = gen(pointer(next), count(tail) + 1);
                    
                    atomic::cas64(_tail, tail, ptr);
                }
            }
        }
        
        //---------------------------------------------------------------
        
        Ptr ptr = gen(node, count(tail) + 1);
        
        atomic::cas64(_tail, tail, ptr);
    }
    
    //-------------------------------------------------------------------
    
    bool pop(T& value)
    {
        Ptr head = null;
        
        //---------------------------------------------------------------
        
        FOREVER {
            head = _head;
        
            Ptr tail = _tail;
        
            Ptr next = pointer(head) -> next;
            
            if (equal(head, _head)) {
                if (pointer(head) == pointer(tail)) {
                    if (pointer(next) == null) {
                        return false;
                    }
                    
                    Ptr ptr = gen(pointer(next), count(tail) + 1);
                    
                    atomic::cas64(_tail, tail, ptr);
                }
                else {
                    value = pointer(next) -> value;
                    
                    Ptr ptr = gen(pointer(next), count(head) + 1);
                    
                    if (equal(atomic::cas64(_head, head, ptr), head)) {
                        atomic::fadd(_pops, 1);
                        
                        break;
                    }
                }
            }
        }
        
        //---------------------------------------------------------------
        
        delete pointer(head);
        
        return true;
    }
    
    //-------------------------------------------------------------------
    
    void done()
    {
        atomic::fsub(_pops, 1);
    }
    
    //-------------------------------------------------------------------
    
    bool busy() const
    {
        return _pops > 0;
    }
    
protected:
    
    uint32 _pops;
    
    Ptr _head;
    
    Ptr _tail;
        
};

//-----------------------------------------------------------------------

#endif // TASKQUEUE_H

Adding a CPUJob interface


You could use function pointers but I like to use objects, firstly I have a CPUJob interface:

//-----------------------------------------------------------------------
// momo (2012)
//-----------------------------------------------------------------------
// file: cpujobbase.h
// author: Victor Egea Hernando (egea.hernando@gmail.com)
// description: 
//-----------------------------------------------------------------------

#ifndef CPUJOBBASE_H
#define CPUJOBBASE_H

//-----------------------------------------------------------------------

#include <core/kernel/data/array.h>

//-----------------------------------------------------------------------
// classes
//-----------------------------------------------------------------------

class CpuJobBase;

//-----------------------------------------------------------------------
// constants
//-----------------------------------------------------------------------

static const uint32 MaxJobs = 64;

//-----------------------------------------------------------------------
// types
//-----------------------------------------------------------------------

typedef Array<CpuJobBase*, MaxJobs> JobArray;

//-----------------------------------------------------------------------
// CpuJobBase
//-----------------------------------------------------------------------

class CpuJobBase
{

public:
    
    virtual CpuJobBase* clone() const = 0; // clone the cpu-job
    
    virtual void exec() = 0;               // process as many tasks as you can
    
    virtual void collectCpuJobData(        // collect cpu-jobs data 
        const JobArray& jobs,              //   cpu-job array
        uint32 njobs                       //   number of cpu-jobs
    ) = 0;
    
};

//-----------------------------------------------------------------------

#endif // CPUJOBBASE_H
    

And a frustum culling cpu-job extension for it:

//-----------------------------------------------------------------------
// CPFCCpuJob: Cross platform frustum culling CPU job
//-----------------------------------------------------------------------

class CPFCCpuJob : EXTENDS(CpuJobBase)
{

public:

    CPFCCpuJob(CameraObject* acamera, const FPlanesTuple& ftuple)
    {
        _acamera = acamera;
        _ftuple = ftuple;
    }
    
    virtual ~CPFCCpuJob()
    {
        
    }
    
    //-------------------------------------------------------------------
    // exec
    //-------------------------------------------------------------------
    
    virtual void exec()
    {  
        KDNode* node = null;
        
        //---------------------------------------------------------------
        
        FOREVER {
            //-----------------------------------------------------------
            // 1.- obtain the tasks
            //-----------------------------------------------------------
            
            if (! g_tasks.pop(node)) {
                if (g_tasks.busy()) {
                    continue;
                }
                else {
                    break;
                }
            }
            
            //-----------------------------------------------------------
            // 2.- check if the aabb intersects
            //-----------------------------------------------------------
            
            const AABB& aabb = node -> aabb();
        
            Vector3 center = ::val<0>(aabb);
            Vector3 hasize = ::val<1>(aabb);
             
            //-----------------------------------------------------------
            
            bool outside = false;
             
            for (uint32 i = 0; i < 6; ++i) {
                Vector4 norfp = ::val<0>(_ftuple).val(i);
                Vector4 absfp = ::val<1>(_ftuple).val(i);
            
                //-------------------------------------------------------
            
                float dr = ::val<0>(center) * ::val<0>(norfp) + 
                           ::val<1>(center) * ::val<1>(norfp) + 
                           ::val<2>(center) * ::val<2>(norfp) +
                           ::val<0>(hasize) * ::val<0>(absfp) + 
                           ::val<1>(hasize) * ::val<1>(absfp) + 
                           ::val<2>(hasize) * ::val<2>(absfp);
            
                //-------------------------------------------------------
            
                if (dr < -::val<3>(norfp)) {
                    outside = true;
                    
                    break; // it's outside
                }
            }
        
            //-----------------------------------------------------------
            // 3.- add the children to the queue or add the node to the
            //     active camera if it is a leaf
            //-----------------------------------------------------------
            
            if (! outside) {
                if (! node -> isLeaf()) {
                    const KDNode::Children& children = node -> children();
            
                    if (::val<0>(children) != null) {
                        push(::val<0>(children));
                    }
            
                    if (::val<1>(children) != null) {
                        push(::val<1>(children));
                    }
                }
                else {
                    _nodes.push_back(node -> leafId());
                }
            }
            
            //-----------------------------------------------------------
            
            g_tasks.done(); // for every successful pop
        }
    }
    
    //-------------------------------------------------------------------
    // collectCpuJobData
    //-------------------------------------------------------------------
     
    virtual void collectCpuJobData(const JobArray& jobs, uint32 njobs)
    {
        _acamera -> clearNodes();
        
        for (uint32 i = 0; i < njobs; ++i) {
            ((CPFCCpuJob*)jobs[i]) -> appendNodes();
        }
    }
     
    //-------------------------------------------------------------------
    // clone
    //-------------------------------------------------------------------
    
    virtual CpuJobBase* clone() const
    {
        return new CPFCCpuJob(*this);
    }
    
    //-------------------------------------------------------------------
    // appendNodes
    //-------------------------------------------------------------------
    
    void appendNodes() const
    {
        _acamera -> appendNodes(_nodes);
    }
     
    //-------------------------------------------------------------------
    // push
    //-------------------------------------------------------------------
    
    static void push(KDNode* node)
    {
        g_tasks.push(node);
    }
    
protected:
        
    static TaskQueue<KDNode*> g_tasks;
        
    //-------------------------------------------------------------------
    
    CameraObject* _acamera;
        
    FPlanesTuple _ftuple;
    
    IndexBuffer _nodes;
        
};

//-----------------------------------------------------------------------

TaskQueue<KDNode*> CPFCCpuJob::g_tasks;

exec is the shared code block by all the threads and has this structure:

    Task task = null;
        
    FOREVER {
        // pop just one task
        
        if (! g_tasks.pop(task)) {
            if (g_tasks.busy()) {
                continue; // wait for more
            }
            else {
                break; // all done
            }
        }
        
        /* DO SOME WORK:
         *  - Append results to the local job structures
         *    This way there is no need to sync anything outside the cpu-job
         */
        
        if (something) {
            // now other cpu-jobs can pick the new task
            g_tasks.push(new_task);
        }
        
        g_tasks.done(); // for every successful pop
    }

Easy, huh? the tasks should be thin and the thread will take full advantage of available CPUs. Notice that there is no sleep before continue, keep in mind that your engine is a realtime application, let the sync to the queue and forget about it. When all threads are finished they will break the loop and go back to their initial status.

When all threads are done, busy is triggered and collectCpuJobData is called for the first job, this will collect the outcome from the cpu-jobs.

How do I exec the frustum culling?

    CPFCCpuJob::push(_root);
    
    CpuJobRunner::instance().exec(
        new CPFCCpuJob(acamera, fpa)
    );

The CpuJobRunner / Thread Pool


I have my own wrappers to hold cross platform functionality for: thread, event-loop, sync

//-----------------------------------------------------------------------
// momo (2012)
//-----------------------------------------------------------------------
// file: cpujobrunner.h
// author: Victor Egea Hernando (egea.hernando@gmail.com)
// description: 
//-----------------------------------------------------------------------

#include <core/kernel/cpujob/cpujobbase.h>

//-----------------------------------------------------------------------

class CpuJobRunnerPrivate;

//-----------------------------------------------------------------------
// CpuJobRunner
//-----------------------------------------------------------------------

class CpuJobRunner
{

public:
    
    static CpuJobRunner& instance();
    
    //-------------------------------------------------------------------
    // main interface
    //-------------------------------------------------------------------
    
    void exec(
        CpuJobBase* cpuJob
    );
    
    void remove(
        uint32 coreId        
    );
    
protected:
        
    CpuJobRunner();
    
    virtual ~CpuJobRunner();
    
    //-------------------------------------------------------------------
    
    static CpuJobRunner* g_instance;
    
    CpuJobRunnerPrivate* d_p;
        
};

//-----------------------------------------------------------------------

//-----------------------------------------------------------------------
// momo (2012)
//-----------------------------------------------------------------------
// file: cpujobrunner.cpp
// author: Victor Egea Hernando (egea.hernando@gmail.com)
// description: 
//-----------------------------------------------------------------------

#include <core/kernel/platform/platform.h>
#include <core/kernel/thread/thread.h>

//-----------------------------------------------------------------------

#include <engine/enginecore.h>

//-----------------------------------------------------------------------

#include <algorithm>
#include <typeinfo>

//-----------------------------------------------------------------------

#include "cpujobrunner.h"

//-----------------------------------------------------------------------
// types
//-----------------------------------------------------------------------

typedef Array<Thread*, MaxJobs> ThreadArray;

//-----------------------------------------------------------------------
// ExecCPUJob
//-----------------------------------------------------------------------

class ExecCPUJob : EXTENDS(Notifier)
{

public:
        
    ExecCPUJob(CpuJobBase* job)
    {
        _job = job;
    }
    
    //-------------------------------------------------------------------
    // notify
    //-------------------------------------------------------------------
    
    virtual void notify() const
    {
        dInfo(Debug::CpuJobInfo, Format("%1 (%2): exec").arg(
                                        Thread::current() -> name(),
                                        typeid(_job).name()));
        
        //---------------------------------------------------------------
        
        _job -> exec();
    }
    
protected:
        
    CpuJobBase* _job;
        
};

//-----------------------------------------------------------------------
// DeleteCPUJob
//-----------------------------------------------------------------------

class DeleteCPUJob : EXTENDS(Notifier)
{

public:
        
    DeleteCPUJob(uint32 coreId)
    {
        _coreId = coreId;
    }
    
    //-------------------------------------------------------------------
    // notify
    //-------------------------------------------------------------------
    
    virtual void notify() const
    {
        dInfo(Debug::CpuJobInfo, Format("%1 (%2): remove").arg(
                                        Thread::current() -> name(),
                                        Format::integer(_coreId)));
        
        //---------------------------------------------------------------
            
        CpuJobRunner::instance().remove(_coreId);
    }
    
protected:
        
    uint32 _coreId;
        
};

//-----------------------------------------------------------------------
// CpuJobRunnerPrivate
//-----------------------------------------------------------------------

class CpuJobRunnerPrivate
{

public:
        
    CpuJobRunnerPrivate();
    
    virtual ~CpuJobRunnerPrivate();
    
    //-------------------------------------------------------------------
    // main interface
    //-------------------------------------------------------------------
    
    void exec(
        CpuJobBase* cpuJob
    );
    
    void remove(
        uint32 coreId        
    );
    
protected:
    
    uint32 _ncores, _ancores;
    
    ThreadArray _threads;
    
    JobArray _jobs;
    
};

//-----------------------------------------------------------------------

CpuJobRunnerPrivate::CpuJobRunnerPrivate()
{
    if (Thread::current() != Thread::main()) {
        dCritical("WindowPane::init should be run in the main thread");
    }
    
    //-------------------------------------------------------------------
    
    _ncores = std::min(Thread::numCores(), MaxJobs);
    
    //-------------------------------------------------------------------
    
    for (uint32 i = 1; i < _ncores; ++i) {
        Format thname = Format("core-%1").arg(Format::integer(i));
                
        _threads[i] = new Thread(thname);

        _threads.at(i) -> setCore(i);
        
        _threads.at(i) -> start();
    }
}

//-----------------------------------------------------------------------

CpuJobRunnerPrivate::~CpuJobRunnerPrivate()
{

}

//-----------------------------------------------------------------------
// exec
//-----------------------------------------------------------------------

void CpuJobRunnerPrivate::exec(CpuJobBase* cpuJob)
{
    if (Thread::current() != Thread::main()) {
        dCritical("WindowPane::init should be run in the main thread");
    }
    
    //-------------------------------------------------------------------
    
    _ancores = _ncores;

    if (Debug::instance().isFlagActive(Debug::ForceSeqCode)) {
        _ancores = 1;
    }
    
    //-------------------------------------------------------------------
    // 1.- create and run the jobs
    //-------------------------------------------------------------------
    
    _jobs[0] = cpuJob;
    
    //-------------------------------------------------------------------
    
    for (uint32 i = 1; i < _ancores; ++i) { 
        //---------------------------------------------------------------
        // 1.1.- create a new cpu-job and reset the status
        //---------------------------------------------------------------
        
        bool success = false;
        
        //---------------------------------------------------------------
        
        for (uint32 j = 0; i < 4; ++j) {
            if (_jobs.at(i) == null) {
                _jobs[i] = cpuJob -> clone();
            
                success = true;
                
                break;
            }
            else {
                if (j < 3) {
                    os::msleep(2);
                }
            }
        }
        
        //---------------------------------------------------------------
        
        if (! success) {
            dCritical(Format("job %1: still active").arg(Format::integer(i)));
        }
        
        //---------------------------------------------------------------
        // 1.2.- run the job
        //---------------------------------------------------------------

        EventLoop::notify(
            "ExecCPUJob", _threads.at(i), new ExecCPUJob(_jobs.at(i))
        );
    }
    
    //-------------------------------------------------------------------
    
    dInfo(Debug::CpuJobInfo, Format("%1: exec").arg(typeid(cpuJob).name()));

    //-------------------------------------------------------------------
    
    cpuJob -> exec();
    
    //-------------------------------------------------------------------
    // 4.- collect the cpu-job data
    //-------------------------------------------------------------------
    
    cpuJob -> collectCpuJobData(_jobs, _ancores);
    
    //-------------------------------------------------------------------
    // 5.- delete the cpu-jobs
    //-------------------------------------------------------------------
    
    for (uint32 i = 1; i < _ancores; ++i) { 
        EventLoop::notify(
            "DeleteCPUJob", _threads.at(i), new DeleteCPUJob(i)
        );
    }
    
    delete cpuJob;
}

//-----------------------------------------------------------------------
// remove
//-----------------------------------------------------------------------

void CpuJobRunnerPrivate::remove(uint32 coreId)
{
    if (_jobs.at(coreId) != null) {
        delete _jobs.at(coreId);
        
        _jobs[coreId] = null;
    }
    else {
         dWarning(Format("the job %1 was already deleted").arg(
                         Format::integer(coreId)));
    }
}

//-----------------------------------------------------------------------
// CpuJobRunner
//-----------------------------------------------------------------------

CpuJobRunner* CpuJobRunner::g_instance = null;

//-----------------------------------------------------------------------

CpuJobRunner::CpuJobRunner()
{
   d_p = new CpuJobRunnerPrivate();
}

//-----------------------------------------------------------------------

CpuJobRunner::~CpuJobRunner()
{
    delete d_p;
}

//-----------------------------------------------------------------------
// instance
//-----------------------------------------------------------------------

CpuJobRunner& CpuJobRunner::instance()
{
    if (g_instance == null) {
        g_instance = new CpuJobRunner();
    }
    
    return *g_instance;
}

//-----------------------------------------------------------------------
// exec
//-----------------------------------------------------------------------

void CpuJobRunner::exec(CpuJobBase* cpuJob)
{
    d_p -> exec(cpuJob);
}

//-----------------------------------------------------------------------
// remove
//-----------------------------------------------------------------------

void CpuJobRunner::remove(uint32 coreId)
{
    d_p -> remove(coreId);
}

//-----------------------------------------------------------------------


And a clean SSE4 cpu-job version


//-----------------------------------------------------------------------
// exec
//-----------------------------------------------------------------------

void SimdFCCpuJob::exec()
{  
    Array<KDNode*, 4> nodes;
    
    Array<AABB, 4> aabbs;
    
    uint32 nnodes = 0;
    
    //-------------------------------------------------------------------
    // prepare the frustum culling data
    //-------------------------------------------------------------------
    
    Array<__m128, 6> xmm_norfp_x, xmm_absfp_x;
    Array<__m128, 6> xmm_norfp_y, xmm_absfp_y;
    Array<__m128, 6> xmm_norfp_z, xmm_absfp_z;
    Array<__m128, 6> xmm_norfp_w;
    
    //-------------------------------------------------------------------
    
    for (uint32 i = 0; i < 6; ++i) {
        Vector4 norfp = ::val<0>(_ftuple).val(i);
        Vector4 absfp = ::val<1>(_ftuple).val(i);
        
        //---------------------------------------------------------------
        
        xmm_norfp_x[i] = _mm_load_ps1(&::get<0>(norfp));
        xmm_absfp_x[i] = _mm_load_ps1(&::get<0>(absfp));
        xmm_norfp_y[i] = _mm_load_ps1(&::get<1>(norfp));
        xmm_absfp_y[i] = _mm_load_ps1(&::get<1>(absfp));
        xmm_norfp_z[i] = _mm_load_ps1(&::get<2>(norfp));
        xmm_absfp_z[i] = _mm_load_ps1(&::get<2>(absfp));
        xmm_norfp_w[i] = _mm_load_ps1(&::get<3>(norfp));
    }
    
    //-------------------------------------------------------------------
    
    FOREVER {
        //---------------------------------------------------------------
        // 1.- obtain the tasks (max 4 kdnodes)
        //---------------------------------------------------------------
        
        bool tryagain = false;
        
        //---------------------------------------------------------------
        
        for (nnodes = 0; nnodes < 4; ++nnodes) {
            if (! g_tasks.pop(nodes.get(nnodes))) {
                if (nnodes == 0) {
                    if (g_tasks.busy()) {
                        tryagain = true;
                        
                        break;
                    }
                    else {
                        return;
                    }
                }
                else {
                    break;
                }
            }
        }
        
        //---------------------------------------------------------------
        
        if (tryagain) {
            continue;
        }
        
        //---------------------------------------------------------------
        // 2.- get the AABBs
        //---------------------------------------------------------------
        
        for (uint32 i = 0; i < nnodes; ++i) {
            aabbs.get(i) = nodes.at(i) -> aabb();
        }
        
        //---------------------------------------------------------------
        // 2.1.- load the 4 center/extents pairs for the 4 AABBs
        //---------------------------------------------------------------
        
        __m128 xmm_c0 = _mm_load_ps(::get<0>(::get<0>(aabbs)).array());
        __m128 xmm_c1 = _mm_load_ps(::get<0>(::get<1>(aabbs)).array());
        __m128 xmm_c2 = _mm_load_ps(::get<0>(::get<2>(aabbs)).array());
        __m128 xmm_c3 = _mm_load_ps(::get<0>(::get<3>(aabbs)).array());
        
        //---------------------------------------------------------------
        
        __m128 xmm_e0 = _mm_load_ps(::get<1>(::get<0>(aabbs)).array());
        __m128 xmm_e1 = _mm_load_ps(::get<1>(::get<1>(aabbs)).array());
        __m128 xmm_e2 = _mm_load_ps(::get<1>(::get<2>(aabbs)).array());
        __m128 xmm_e3 = _mm_load_ps(::get<1>(::get<3>(aabbs)).array());
        
        //---------------------------------------------------------------
        // 2.2.1.- shuffle the data in order (xy)
        //---------------------------------------------------------------
        
        __m128 xmm_c0xyc1xy = _mm_shuffle_ps(
            xmm_c0, xmm_c1, _MM_SHUFFLE(0, 1, 0, 1)
        );
        
        __m128 xmm_c2xyc3xy = _mm_shuffle_ps(
            xmm_c2, xmm_c3, _MM_SHUFFLE(0, 1, 0, 1)
        );
        
        __m128 xmm_c0123x = _mm_shuffle_ps(
            xmm_c0xyc1xy, xmm_c2xyc3xy, _MM_SHUFFLE(3, 1, 3, 1)
        );
        
        __m128 xmm_c0123y = _mm_shuffle_ps(
            xmm_c0xyc1xy, xmm_c2xyc3xy, _MM_SHUFFLE(2, 0, 2, 0)
        );
        
        //---------------------------------------------------------------
        
        __m128 xmm_e0xye1xy = _mm_shuffle_ps(
            xmm_e0, xmm_e1, _MM_SHUFFLE(0, 1, 0, 1)
        );
        
        __m128 xmm_e2xye3xy = _mm_shuffle_ps(
            xmm_e2, xmm_e3, _MM_SHUFFLE(0, 1, 0, 1)
        );
        
        __m128 xmm_e0123x = _mm_shuffle_ps(
            xmm_e0xye1xy, xmm_e2xye3xy, _MM_SHUFFLE(3, 1, 3, 1)
        );
        
        __m128 xmm_e0123y = _mm_shuffle_ps(
            xmm_e0xye1xy, xmm_e2xye3xy, _MM_SHUFFLE(2, 0, 2, 0)
        );
        
        //---------------------------------------------------------------
        // 2.2.2.- shuffle the data in order (z)
        //---------------------------------------------------------------
        
        __m128 xmm_c01z = _mm_shuffle_ps(
            xmm_c0, xmm_c1, _MM_SHUFFLE(2, 0, 2, 0)
        );
        
        __m128 xmm_c23z = _mm_shuffle_ps(
            xmm_c2, xmm_c3, _MM_SHUFFLE(2, 0, 2, 0)
        );
        
        __m128 xmm_c0123z = _mm_shuffle_ps(
            xmm_c01z, xmm_c23z, _MM_SHUFFLE(3, 1, 3, 1)
        );
        
        //---------------------------------------------------------------
        
        __m128 xmm_e01z = _mm_shuffle_ps(
            xmm_e0, xmm_e1, _MM_SHUFFLE(2, 0, 2, 0)
        );
        
        __m128 xmm_e23z = _mm_shuffle_ps(
            xmm_e2, xmm_e3, _MM_SHUFFLE(2, 0, 2, 0)
        );
        
        __m128 xmm_e0123z = _mm_shuffle_ps(
            xmm_e01z, xmm_e23z, _MM_SHUFFLE(3, 1, 3, 1)
        );
        
        //---------------------------------------------------------------
        // 3.- check if the aabb intersects
        //---------------------------------------------------------------
        
        __m128 xmm_tmp, xmm_dpr;
        
        //---------------------------------------------------------------
        
        uint32 mask = 0;
        
        //---------------------------------------------------------------
        
        for (uint32 i = 0; i < 6; ++i) {
            xmm_dpr = _mm_mul_ps(xmm_c0123x, xmm_norfp_x[i]);
            xmm_tmp = _mm_mul_ps(xmm_c0123y, xmm_norfp_y[i]);
            xmm_dpr = _mm_add_ps(xmm_dpr, xmm_tmp);
            xmm_tmp = _mm_mul_ps(xmm_c0123z, xmm_norfp_z[i]);
            xmm_dpr = _mm_add_ps(xmm_dpr, xmm_tmp);
            xmm_tmp = _mm_mul_ps(xmm_e0123x, xmm_absfp_x[i]);
            xmm_dpr = _mm_add_ps(xmm_dpr, xmm_tmp);
            xmm_tmp = _mm_mul_ps(xmm_e0123y, xmm_absfp_y[i]);
            xmm_dpr = _mm_add_ps(xmm_dpr, xmm_tmp);
            xmm_tmp = _mm_mul_ps(xmm_e0123z, xmm_absfp_z[i]);
            xmm_dpr = _mm_add_ps(xmm_dpr, xmm_tmp);

            //-----------------------------------------------------------
            
            mask |= _mm_movemask_ps(
                _mm_add_ps(xmm_dpr, xmm_norfp_w[i])
            );

            //-----------------------------------------------------------
            
            if (mask & 0xf == 0xf) {
                break; // all are outside
            }
        }
        
        //---------------------------------------------------------------
        // 3.- add the children to the queue or add the node to the
        //     active camera if it is a leaf
        //---------------------------------------------------------------
        
        for (uint32 i = 0; i < nnodes; ++i) {
            if (! ((mask & (1 << i)) >> i)) {
                if (! nodes.at(i) -> isLeaf()) {
                    const KDNode::Children& children = nodes.at(i) -> children();
                    
                    if (::val<0>(children) != null) {
                        push(::val<0>(children));
                    }
                    
                    if (::val<1>(children) != null) {
                        push(::val<1>(children));
                    }
                }
                else {
                    _nodes.push_back(nodes.at(i) -> leafId());
                }
            }
            
            g_tasks.done(); // for every successful pop
        }
    }
}

Performance


Tested loading a scene with a very dense kd-tree (time in ms).


Attached Image: performance.png


Conclusion


Four threads are not able to beat SSE4, even with a fine grained approach. SSE4 is much more stable than using parallel programming, nonetheless more cores can provide an extra boost to the frustum culling, and with some parameter adjustments the user can even decide what kind of optimization is going to be used.

Designing a good framework for parallel algorithms isn't that difficult using lock-free queues, and you can integrate it into your own engine without too much trouble.

Bibliography:

· http://www.valvesoftware.com/publications/2007/GDC2007_SourceMulticore.pdf

Autodesk Maya 2014 Review

$
0
0

Introduction


Autodesk Maya 2014 software is available as a stand-alone product or as part of a Suite. The Maya Entertainment Creation Suite 2014 includes Maya, Mudbox, MotionBuilder and Sketchbook Designer. There is also a Premium Suite offering, which adds Softimage to the mix and an Ultimate Suite offering, which adds 3ds Max.

The new version of Maya 2014 has lots of small performance enhancing improvements and several larger new features that make the software easier to use. In particular, the new Grease Pencil tool and the Modeling Toolkit are worth looking into. There is also a new Mesh Reduction feature and several interesting new options for working with Paint Effects.

Interface Improvements


The 2014 version of Maya has some subtle changes to the interface that are nice improvements. Maybe it's just the reviewer in me talking, but I love the new What's New Highlight Settings. All the new menus and tools are highlighted with a green set of brackets when this option is enabled. This makes it easy to see where the new features are located. The setting can also easily be disabled once you are comfortable with the new features.

The Head-Up Display feature has a new option called In-View Messages. These messages show up giving tips and explanations of different options as you use certain tools. The messages are often helpful, but I wish there were more of them.

Tooltips are great for helping to identify all the various tools, but now they also include ToolClips, which are expanded information dialogs and video clips explaining how to use the tool. These are great when you are first starting out, but can get old for more seasoned users.

Another bunch of interface improvements can be found in the Node Editor. You can now press the 5 key to cycle between the Node Name, the Node Type or nothing. Each node type is displayed using a different color making it easier to find and identify different node types. The various node ports are also color coded making it easier to make the correct connections. And, of course, you can change these default colors to whatever makes sense to you.

There is a text line beneath each node that lets you type in text to filter the attributes that are shown. Another cool feature is that when a port is selected, only those ports that can accept the connection are made available, all others are grayed out.

Grease Pencil Tool


Even though it is a simple tool, I've found the new Grease Pencil tool extremely valuable. This tool lets you create a frame overlay and draw on it with a brush like an transparent piece of plastic positioned on top of the current scene, as shown in Figure 1. The tool includes modes for drawing with a Pencil, Marker and Soft Pencil, along with an Eraser. The drawn lines show up as the animation is played and is great for writing visual clues and reminders for current work. You can also turn ghosting of previous and post-frame images on and off.


Attached Image: Figure 1 - Grease pencil.jpg
Figure 1: The Grease Pencil tool lets you draw in 2D on the current scene over several frames.


The tool designers envision it being used to compose shots, animation lines and to quickly block-out poses, but I found it was a wonderful review tool allowing me to make visual notes on another person's animation. Since it is drawn right on the screen. It is useful to show where the character needs to move and how to change the model proportions providing accurate feedback for animators and modelers without messing with the scene models. Any drawn lines and notes can be easily turned off and discard as work proceeds.

Modeling Toolkit


My favorite new addition to the latest version of Maya has got to be the Modeling Toolkit. This Toolkit shows up as a floating panel where the Attribute Editor usually is and it has many of the key selection and modeling tools at your fingertips.

It lets you choose the components at the top of the panel and selections can be switched between the different component modes, so if you have a selection of edges and you want to move all the adjacent faces, you simply need to select Face Selection mode while holding down the Ctrl key and all the faces connected to the selected edges are magically selected. There is also a Multi-Component mode that lets you work with vertices, edges and faces at the same time.

It also keeps a count of the number of components selected so you can quickly see if you have any components on the backside of the model selected. The panel also has all the transformation options available for quick selection along with several selection options such as Pick, Raycast and Highlight Backfaces. Beneath the selection options is a button for selecting symmetrical components about the model's axes. There are also several Selection and Transform Constraints that you can use to slide along the edge or surface of the mesh.

With the Mesh Editing Tools section are several awesome tools for quickly performing specific operations such as Bevel, Bridge, Connect, Extrude, Multi-Cut, Target Weld, and Quad Draw. Each of these tools has several options. For example, the Bridge tool is used to create faces that bridge together the selected edges on separate objects. Think of two spheres that could be made into a barbell quickly by bridging the edges of both spheres closest to each other. The tool also has options to specify the number of divisions, the taper and twist along the length of the bridge and an offset value.


Attached Image: Figure 2 - Modeling toolkit.jpg
Figure 2: The Modeling Toolkit has many key selection and modeling tools in the same panel.


The Quad Draw tool is another marvelous tool that is easy to use. Simply select it and click to place vertices on the home grid or on the surface of a selected object, then hold shift and move the mouse over the vertices to see a preview of the new quad face. Then, simply click to create the face. This is a great tool for creating models from scratch or for resurfacing an existing mesh.

The Modeling Toolkit doesn't have everything you need, but the tools it does have are a good starting point and they are convenient for most of the quick basic roughing aspects of modeling without having to dig through the menus, which are becoming more and more crowded.

Other Modeling Improvements


The Modify, Convert menu has a new option to Convert Geometry to Bounding Box. This provides an easy way to quickly reduce the resolution of complex models for animation timing and for creating proxy objects.

The Mesh Reduce feature has been improved allowing you to reduce the number of polygons in a mesh without changing the basic shape of the model. It also now offers options to keep your existing mesh, UV, color and material borders and hard and crease edges. You can also select to reduce by percentage, vertex count or triangle count. There is also a Slider to set the Preserve Quads value, which keeps the quads in place if you like modeling with edge loops. Figure 3 shows a high-res lamp that was reduced from over 27,000 faces to 2000 face while maintaining its shape.


Attached Image: Figure 3 - Reduced lamp mesh.jpg
Figure 3: The Reduce feature lets you aggressively reduce the number of faces in a mesh without altering its basic shape.


The Edge Flow tool has been improved an option to Insert with Edge Flow that adds edges to the mesh that matches the current curvature of the mesh. You can also alter the curvature of the selected edges with the Edit Edge Flow value.

The new Slide brush, which is part of the Sculpt Geometry Tool, lets you push around vertices as you move over them with the brush and the best part is that they stay true to the current model shape. This lets you quickly move vertices from a less important part of the model, like the top of the head, to the place where they are needed more, like the forehead or around the eyes and ears.

There is also a new Crease Set Editor that hold edge selections where the crease needs to be maintained.

Improvements with Paint Effects


Paint Effects have always been awesome at layering details on top of existing meshes, but the Paint Effect tubes always seem to have a mind of their own that didn't play nice with the scene mesh objects. Paint Effects have been improved in Maya 2014 with options to snap to the surface of a mesh. This lets the tubes be aware of the mesh surface and adhere right to it (Figure 4). There is also a Surface Collide option that prevents the tubes from intersecting with the mesh surface. Finally, there is a Surface Attract option that causes the tubes to move towards an object like flowers reaching for the sky.


Attached Image: Figure 4 - Climbing roses.jpg
Figure 4: The new Make Collide and Surface Snap options allow Paint Effects to closely follow a selected object.


Another improvement defines how the Paint Effects grow within a volume. Using the Occupation Volume option you can have the Paint Effect tubes stay confined within a given area creating more realistic bushes and plants. Finally, for Paint Effects, are the new options to randomize the size of leaves and flowers on a plant.

Scene Assembly


For those large scale projects where your machine grinds to a halt because of all the assets that it has to work with, the new Scene Assembly features are helpful. Although it takes some time to save and name alternate versions of the scene assets, once they are in place, you can quickly swap out high-res versions for reasonable proxy versions using Alembic-based caches.

This lets you generate pre-visualization scenes and verify animation, lighting and scene layouts before wasting rendering resources to find the problem areas.

Rendering Improvements


The Rendering improvements for Maya 2014 involve both the rendering output and the viewports. The big news is support for DirectX 11 along with support for HLSL shaders. This includes and UberShader that has advanced options for displaying displacement, translucency, and blurred reflections directly in the viewport.

Dynamics Improvements


For the Dynamics side of Maya 2014, you can now add an nHair system to an nCloth system and the collisions are detected both ways. You can also use the new Collide with Mesh option for nHair systems. There is also an option to fill a geometry mesh with fluid so that the fluid takes the shape of the mesh.

SAT Improvements


The Maya development team keeps a list of what it has coined as Small Annoying Things (SAT). These are the small changes that bug users and the team has a project to identify and improve on these requests which come directly from users.

For Maya 2014, several SAT requests that have been addressed include changes to the Interactive Split Tool, the ability to edit edge flow, displaying vertex colors in the viewport, showing notes in the Attribute Editor, a new Increment and Save button and panning in the Outliner.

Autodesk makes the full list of current SATs available online and encourages users to help identify problems with the software that need to be addressed.

Summary


It's great to see a list of improvements that were specifically requested by Maya users. This shows Autodesk's commitment to the users. It's also great to see a simplified panel like the Modeling Toolkit that makes modeling easier by placing a set of only what you need in a small convenient place. All of the improvements together make Maya 2014 an even stronger package than ever before.

Maya 2014 is available for Windows, Linux, and Macintosh OS X. For more information on any of these products, visit the Autodesk web site located at http://www.autodesk.com. A free trial version is also available at http://www.autodesk.com/freetrials.

GPU Texture Snapshot

$
0
0
As you know, 3D graphics engineering deals with a lot of textures. And often, you need to see what’s in those textures. Especially if they are the result of a rendering pass like your depth buffer, G-buffer… However, seeing what’s in the textures while your program is running is a bit of a challenge. Usually, you have to dump texture data into a file format that can be opened by an image viewer.

I have always found the task of viewing GPU generated textures to be complicated and unnecessary. What if I could just display the texture on the screen? Just like printf does with text!

Printf


Any programming language has the equivalent of C/C++ printf. For instance, every “Hello World!” program uses a print function to output text to a console, file or web page… I thought about what it would take to have a similar API that works for images, the same way printf works for text. The API would have to be:
  • Easy to use like printf.
  • Immediate. You should see your images (somewhere) as soon as the print function is called.
  • Unobtrusive. It should not alter a program’s flow or dramatically affect its performance.
  • It should rely on technology that is native to any system.
That being said, printf has a weakness that would be unsuitable for the goal of printing images. Every time code execution goes over a printf call, text is printed. This is often undesirable. Without any control mechanism, calling printf (for instance, in a loop) ends up flooding the text output outlet. Since images can be large in size, continuously printing them out of a running program could have a dramatic effect on performance.

Designing Printf For Images


What if you could tell a program to export raw image data at a moment of your choosing? This would be an improvement over the way C/C++ printf works. An image would be exported out of a program when the developer chooses to while the rest of the time the program would simply run unaffected.

The solution I designed, has three parts.
  • Just like C/C++ printf has a command line terminal as default output, I needed an outlet for images and I think Pico Pixel desktop application would be perfect for it. Pico Pixel is an image viewer we created. It is fast, simple, supports many pixel formats (DXT, BCn, ETC2…) and file formats such as OpenEXR, HDR, KTX and more.
  • A C/C++ API. Developers call functions of the API to export images out of programs. The API is written in simple C/C++ that any compiler can handle.
  • A communication system between a program and Pico Pixel. Network sockets are good for the job. Sockets libraries are everywhere. They work across hardware platforms and OSes. They are very well documented.
The result is Pico Pixel SDK. The SDK is made of a few files (open source) you drop in your build environment. They should compile without much trouble. To use the SDK you instantiate PicoPixelClient object and open a connection to Pico Pixel desktop application. The method you use to send pixel data to Pico Pixel is called PixelPrintf.

SDK Integration


As I said before, calling printf in C/C++ always outputs text in the console and this is not desirable for images. To prevent images from being sent everytime code execution goes over a call to PixelPrintf, we need something called a marker.

Markers are objects used by PixelPrintf to decide whether to send image data to Pico Pixel or not. Every marker has an associated counter of integer values. While a marker's counter value is greater than 0, any call to PixelPrintf with the marker will be carried through. Each time a marker is used, its counter value is decremented by one.

When a marker's counter value reaches zero, the marker can no longer be used to send image data to Pico Pixel. Its counter value has to be reloaded before it can be used again. Pico Pixel provides a user interface to reload counters. As a result, Pico Pixel acts as the trigger for image snapshot in a program.

Here is what the code looks like.

PicoPixelClient pico_pixel_client("GlxGears");

pico_pixel_client.StartConnection();

// the last parameter '0' means the marker use_count is 0.
int marker = pico_pixel_client.CreateMarker(std::string("Color Buffer"), 0);
int marker = pico_pixel_client.CreateMarker(std::string("Depth Buffer"), 0);

// Send the defined markers to PicoPixel desktop application
pico_pixel_client.SendMarkersToPicoPixel();

// Image data is sent if and only if the marker's use_count is greated than 0.
// A marker's use_count is decremented each time the marker is used.
pico_pixel_client.PixelPrintf(
    marker,                                 // data marker
    "color-framebuffer",                    // image name to appear in Pico Pixel desktop application
    PicoPixelClient::PIXEL_FORMAT_BGR8,     // image pixel data format
    400,                                    // image width
    300,                                    // image height
    1200,                                   // image row pitch in bytes
    FALSE,                                  // set to TRUE if the data is in srgb 
    FALSE,                                  // set to TRUE to rotate the image horizontally when displayed
    raw_data                                // char* pointer to the image raw data
    );

By instrumenting a program with the code above, Pico Pixel interface mirrors the markers that have been defined.

Attached Image: PixelPrintf.png

To take a snapshop of a texture buffer, set a value greater than 0 in the marker used to export the surface. When markers in Pico Pixel are synchronized with markers in the program, images are sent whenever code execution goes over the PixelPrintf call that use markers with positive counter's values.

Conclusion


The best feature of Pico Pixel SDK is that you have a lot of control over images and the moment they are exported. When images are not exported, your program is unaffected by the presence of the SDK.

I am very excited about this feature. It makes Pico Pixel useful in a graphics developer’s toolset. The idea of sharing images over the network between applications is not a new one. However, for us graphics developers, it opens a lot of possibilities if we take advantage of it easily during development. I believe this is a good feature to have and I hope it will save you time in efforts.

Concentration of all The Lessons in Common Library GLSummary in MFC

$
0
0

Explaining the Concept


Not a long time ago I’ve found the Russian site of NeHe’s OpenGL lessons and thus my study of OpenGL commenced. This is a great idea of people’s tutorial on OpenGL. I hope this lesson will help to a lot of users in OpenGL and MFC. While I’ve adopted the codes of the lessons above one by one to Microsoft Visual Studio the idea of their integration aroused for the compilation of the programs of my own.

Download the source files

And already after this job to be completed I’ve found out the fine adaptation of all the lessons to MS Studio .NET (Grant James) on the NeHe GameDev site, but not adopted to Microsoft Foundation Classes (MFC). As for my point of view MFC have much more possibilities for the application development. I’ve adopted almost all the codes of the lessons one by one in LessonsGLCode directory and integrated them in one common program GLSummary. While adaptation of the codes I found some conflicts occurred in projects modification from one version of MS Visual Studio to another. Finally in demonstration purpose I’ve developed the projects as follows (references on me appreciated but not compulsory):
  • GLSummaryCode_2010 – concentration of all the lessons in one project;
  • LessonsGLCode_2010 - projects of all the lessons (you may call any lesson from another one).
Here 2010 means that projects have been developed for Microsoft Visual Studio Professional 2010. Nevertheless all the projects easily jump into version 2012, but for this version Windows 7 required while version 2010 available in Windows XP SP3 and higher.

The Projects above have been developed with the great help of the editor of the Russian site of NeHe Sergey Anisimov whom I thank very much for his kind assistance.

The codes above may give a great help for the people who want to study Visual C++ from the beginning. Needless to say that the best way to study any compiler is to start learning with some working projects.

Presentation of this lesson refers to GLSummaryCode_2010. However there will not appear too many differences in version 2012.

The codes of the lessons adapted to MFC are concentrated in GLSummaryCode_2010 directory. The main project for this lesson is GLSummary. All the initial data for this project and all the others are integrated in Data directory. For codes view and edit GLSummary.sln file to get started. Even if you have no experience in using Visual C++ you may commence to learn it by starting this (or any other enclosed) project. The common procedures for all the projects are located in GlobUse directory.

The list of the procedures used in the project to be found by menu View->Solution Explorer. Just select any *.h or *.cpp file and you may look at and edit it. At the first sight it seemed that there are too many files enclosed but further consideration shows that everything is not too complicated.

Compile the program (menu Build->Build Solution) and start it (menu Debug->Start Without Debugging). If everything true done the demonstration of the one of the lessons to be commenced (the lesson which name is stored in LastNum.txt file in LessonsGL/GLSummary directory). For any other lesson demonstration select menu File->Lesson... The dialog to be appeared:


Attached Image: MenuLessonsGL.JPG


You may select any other lesson from the list for demonstration.

For the independent application creation:
  • create new directory (say, GLTest);
  • copy program GLSummary.exe(located in ../GLSummary/Release directory) into this new directory;
  • copy Data, Art и BoxData directories into this new directory and application is ready for use.
GLSummary.cpp, GLSummaryDoc.cpp, GLSummaryView.cppion, GLSummary.rc, resource.h files have been created by Visual C++ compiler while project creation. Mainfrm.cpp file initially also has been created by Visual C++ compiler but later it was moved into GlobUse directory for common use in all the projects.

In the GLSummaryView.cpp file and in all the projects in the *View.cpp files procedure CreateGLWindow to be called by virtual procedure OnCreate:

int CGLSummaryView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
	if (CView::OnCreate(lpCreateStruct) == -1)
		return -1;
	CreateGLWindow(this,32);
	return 0;
}

Initialization procedure InitGLn to be called by virtual procedure OnInitialUpdate:

void CGLSummaryView::OnInitialUpdate()
{
	CView::OnInitialUpdate();
	FILE * f = fopen("LastNum.txt","rt");	//Get last lesson name
    if(f != NULL)
	{
		fgets(str, 256, f);
		fclose(f);
		CString tString = str;
		m_strNum = strtok(str, ". _-:");
		m_strLesson = tString;
       m_pDlgTitle = new CDlgTitle;		
	m_pDlgTitle->SetTitle(tString);
	}  
	InitGLn();
	GetDocument()->SetTitle(GetLessonName(m_strNum));
}

The string variable m_strNum is global and serves as identification of the lesson selected. Procedure ResizeGLScene to be called by virtual procedure OnSize:

 void CGLSummaryView::OnSize(UINT nType, int cx, int cy)
{
	CView::OnSize(nType, cx, cy);

	ReSizeGLScene(cx,cy);
	GetMainFrameGlob->SetTitlePos();
}

The image performance maintains by DrawGLSceneN to be called by virtual procedure OnDraw:

void CGLSummaryView::OnDraw(CDC* /*pDC*/)
{
DrawGLSceneN();
}

or by virtual procedure OnTimer:

void CMainFrame::OnTimer(UINT nIDEvent)
{
		switch(nIDEvent)
{
 case TIMER_PLAY:
	UpdateGLScene();
	DrawGLSceneN();
  break;
}
	CFrameWnd::OnTimer(nIDEvent);
}

or after some actions performed by user. The attributes changes for image performance maintains by UpdateGLScene procedure to be called by virtual procedure OnTimer. The procedures WndProc and WinMain are not required in MFC. The event handler for Visual C++ in MFC to be used instead WinMain procedure in original lessons.

E.g., handling of 'L' key pressed executed by CGLSummaryView::OnVkL:

void CGLSummaryView::OnVkL(){ProcessKeyboardN("L");}

This procedure has been created by AppStudio (menu View->Resource View). I will not describe the order of the creation of event handler (see the details in MSDN Library). Full screen view in difference with the original lessons occurred by simultaneous pressing Ctrl+Enter and maintained by CMainFrame::OnVkReturnCtrl procedure:

void CMainFrame::OnVkReturnCtrl()
{
	m_bFullScreen  = ! m_bFullScreen;
if (m_bFullScreen) 
{
    ModifyStyle(WS_CAPTION, 0);
	m_wndToolBar.ShowWindow(SW_HIDE);
	m_wndStatusBar.ShowWindow(SW_HIDE);
 	ShowWindow(SW_SHOWMAXIMIZED);
	HideMenu();
}
else
 {
   ModifyStyle(WS_CAPTION, WS_OVERLAPPED | WS_CAPTION | FWS_ADDTOTITLE
		| WS_THICKFRAME | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_MAXIMIZE); 
	ShowMenu();
	m_wndToolBar.ShowWindow(SW_SHOWNORMAL);
	m_wndStatusBar.ShowWindow(SW_SHOWNORMAL);
	ShowWindow(SW_SHOWNORMAL);
}//if (m_bFullScreen)
}

F1 key in difference with the original lessons is used for standard use for help call (menu Help->GL Help Summary) and maintained by CMainFrame::OnHelp():

void CMainFrame::OnHelp()
{
	if(m_pAboutDlg == NULL)
		m_pAboutDlg = new CAboutDlg();
    m_pAboutDlg->ShowWindow(SW_SHOWNORMAL);

Dialog window m_pAboutDlg is non-modal thus the program handling may be performed as from this window and/or by the other ways:


Attached Image: HelpLessonsGL.JPG


One more differences with the original lessons is that all the texture procedures integrated in GlobGLTexture.cpp file in GlobUse directory. Just I’ve added LoadGLTextureImage(CString fName) procedure which allow to load in texture any image file with the help of the class CImage:

int LoadGLTextureImage(CString fName) 
{
if(!isFileExist(fName))
{
AfxMessageBox("LoadGLTextureImage\nNo File exist:\n" + fName); 
return FALSE;
}
CImage * pImg = new CImage; 
pImg->Load(fName);
CBitmap * pBm = GetImgBitmap(pImg);
if(pBm == NULL)
return FALSE;
BITMAP BMP;
pBm->GetBitmap(&BMP);
GLuint nt = LoadGLTexture(BMP.bmWidth, BMP.bmHeight,GL_BGR_EXT, BMP.bmBits);
delete pBm;delete pImg;
pImg = NULL; 
return nt;
}

You may copy GlobGLTexture.cpp file and use it for any projects using OpenGL development.

The lesson's codes themselves are presented in Solution Explorer window as *DrawInit.cpp files. These are the codes presented in NeHe’s GameDev site (Grant James) and as far as possible I’ve tried not to change the codes while adaptation to MFC.

The only big difference is that I dare to exclude Glaux.lib library from all the projects. Nevertheless I hope the authors of the original codes will not complain. Sure they could not expect from MS VS developers that Glaux.lib library to be excluded from 10th version commenced. And if it is still possible to include Glaux.lib in the MS VS 10th version in the 11th version it is not working.

The concentrations of all the procedures in one common directory GlobUse make it possible to use and combine common procedures in separate lessons and also to develop new applications based on original lessons. The samples of application development are presented in projects lessonX4/UFOgame and lessonX5/BoxMan. Lesson X4.UFO Games combined from the lessons 07, 11, 30, 32, 34. Lesson X5.BoxMan demonstrates the possibility of figures and their movements creation in outer text files and the user no need to know programmer details space imagination enough.

I think that the idea of OpenGL study with the help of the ready working projects completely fit to the people’s tutorial on OpenGL. I have not too muсh experience OpenGL handling and two of the lessons I’ve failed to adapt to MFC. I hope that for respected authors of the original codes will not be difficult to improve my codes to fit them completely to their original intentions.

Game i18n via IME

$
0
0
Internationalization (usually shortened i18n) is a clear requirement for any successful game and application. Especially when players are allowed to interact (chat) with each other, it is essential that they do it in their mother tongue. While such a requirement is relatively easy for some western languages, others like East Asian ones require the use of Input method editors (IME) that translate multiple keystrokes in final glyphs displayed on the screen.

In this post I’ll describe the basics of an IME integration in a game and some of the challenges that it poses. Recently we added full IME support to Coherent UI and most of the pain points were experienced first-hand.

Although most modern font rendering libraries have no issues showing Unicode content, generating it is another story. On most operating systems it relies on IME functionality. The input method detects the currently selected language and as the user types some keys (some support mouse input too), it automatically proposes a valid character for that combination. For instance the Pinyin input method relies on the user entering the pinyin of a Chinese character to receive a list of compatible final characters that otherwise would be impossible to write.


ime.png
Some of the components during an IME composition


While the user types characters an “IME composition” is started – that means that the combination might produce different outputs and it is not committed to the text field until the user accepts it (usually by pressing ‘space’). The composition can also be discarded and restarted. In the screenshot above the “Composition string” is not final but shows an intermediate representation (that’s why it’s underlined). Part of the input has already been translated to Chinese characters while some of it remains to be defined.

The possible Chinese characters in our example that match the current Latin characters are given in the “Candidate list”. The user can select in it, either though a number, or with the mouse. PgUp/PgDn can scroll the candidate list to show more options.

Most IME methods are fairly complex pieces of software, so implementing an ad-hoc one in your application, although possible, is not something I’d recommend. Usually we rely on the OS to facilitate the use of the currently selected IME.

In this post I’ll assume that we use the Chinese Simplified Pinyin input method. Other languages like Japanese and Korean have some differences.

How to communicate and show the various IME-related data is platform-specific and more or less well documented in the API references. All relevant systems (Windows, Mac OS X, Linux) have default implementations for handling IME input – they can show the composition window, the candidate list and submit the text to the application. If you are creating a standard window with text fields you can safely use the system implementation. The default code works because the stock controls have already been written so that they interact properly with the input method.

What we want to do however is to implement IME in an application like a game that:
  • usually renders fullscreen
  • does not use any of the default OS input controls
Imagine an OpenGL game where you have to input the player name. You could still try to rely on the OS to draw and show the IME-related windows but the result will be very awkward. First your IME composition string and candidate list will appear where your window has it’s origin (the OS has no way of knowing where your text field is on-screen). Fullscreen applications on Windows at least will struggle showing the IME windows and an extremely noticeable and unpleasant z-fighting will begin between the application and the IME windows. Finally if the user is playing the game we don’t want him start typing IME characters, so the OS should somehow know which key events to ignore.

We need to accomplish a list of tasks to have a good, reliable IME implementation:
  • Draw as much IME-related information yourself (candidate list, composition window etc.) as possible. This has the added benefit that you can style it in any way you want.
  • Notify the OS when the game is in “typing” mode – the user is writing something in a text field.
  • Notify the OS when the user has cancelled the current composition by un-focusing the text field without committing the text.
  • Accept notifications by the OS when the composition has been committed or cancelled and update the UI accordingly.
  • Read the OS hints about the notification string and display them. For instance you could underline the current on-going composition as a hint to the user.
Drawing the IME-related windows shouldn’t be a problem for any capable UI library. On Windows you must also pay attention when the candidate list changes – when the user pushes PgUp/PgDn during a composition, the list might change and a Windows message is received. Also when the numbers 1-9 are pushed, a candidate is selected and the text committed. On most systems you can also select the candidate by clicking it with the mouse. Users are accustomed to this and you should provide the functionality too.

Your UI library must support a way to tell you three things:
  • Is a text input field on focus now – so that we can enable IME on the OS level and start listening to it’s events.
  • Where is the caret – so that we can position the candidate list under it, as users are accustomed to.
  • The user has changed the focus to something without text input capabilities – so that you can tell the OS to cancel the composition.
Additionally the library should have some notion of “composition string” – that is a temporary string marked visually in some way, that should be thrown away as soon as the user commits the composition and replaced with the final characters. Upon composition cancel, it should just be deleted.

These requirements appear simple enough but might require substantial coding effort. For reference you could see the implementation of the IME(CustomUI) sample in the DirectX SDK – just their CDXUTIMEEditBox class stands at 1000 lines of code – almost all of it IME-related.

Alas the OS interaction could also be somewhat tricky – especially if you need to support all Desktop platforms. The way things are done on Windows are radically different from Linux and Mac OS X. Although the verbosity of the Windows API in the IME-related stuff might be off-putting in the beginning, it is by far the most sane.

In conclusion good IME support is mostly a matter of how flexible your UI library is. The OS plumbing is tricky but manageable. The UI-related requirements however can become quite difficult for simple libraries.

References

Using an Input Method Editor in a Game

DirectX Graphics Diagnostic

$
0
0

Debugging a captured frame is usually a real challenge in comparison with debugging C++ code. We are dealing with hundreds of thousands of more, pixels that are produced, and in addition, there might be several functions being processed by the GPU. Typically, in modern games, there are different passes on a frame constructing it; also, there are many post-process renderings that will be applied on the final result to increase the quality of the frame. All these processes make it quite difficult to find why a specific pixel is drawn with an unexpected color during debugging! Visual Studio 2012 comes with a series of tools that intend to assist game developers. The new DirectX graphics diagnostics tools are a set of development tools integrated with Microsoft Visual Studio 2012, which can help us to analyze and debug the frames captured from a Direct3D application. Some of the functionalities of these tools come from the PIX for a Windows tool, which is a part of DirectX SDK.


note that the DirectX graphics diagnostics tools are not supported by Visual Studio 2012 Express at the time of writing this article.


In this article, we are going to explain a complete example that shows how to use graphics diagnostics to capture and analyze the graphics information of a frame. Open the final project of this article, DirectX Graphics Diagnostics, and let's see what is going on with the GPU.


Intel Graphics Performance Analyzer (Intel GPA) is another suite of graphics analysis and optimization tools that are supported by Windows Store applications. At the time of writing this article, the final release of this suite (Intel GPA 2013 R2) is able to analyze Windows 8 Store applications, but tracing the captured frames is not supported yet. Also, Nvidia Nsight™ Visual Studio Edition 3.1 is another option which supports Visual Studio 2012 and Direct3D 11.1 for debugging, profiling, and tracing heterogeneous compute and graphics application.


Capturing the frame


To start debugging the application, press Alt + F5 or select the Start Diagnostics command from Debug | Graphics | Start Diagnostics, as shown in the following screenshot:


4803_04_07.png


You can capture graphics information by using the application in two ways. The first way is to use Visual Studio to manually capture the frame while it is running, and the second way is to use the programmatic capture API. The latter is useful when the application is about to run on a computer that does not have Visual Studio installed or when you would like to capture the graphics information from the Windows RT devices.


For in the first way, when the application starts, press the Print Screen key (Prt Scr).


For in the second way, for preparing the application to use the programmatic capture, you need to use the CaptureCurrentFrame API. So, make sure to add the following header to the pch.h file:


#include <vsgcapture.h> </p>

For Windows Store applications, the location of the temp directory is specific to each user and application, and can be found at C:\users\username\AppData\Local\Packages\package family name\TempState\.


Now you can capture your frame by calling the g_pVsgDbg->CaptureCurrentFrame() function. By default, the name of the captured file is default.vsglog.


Remember, do not start the graphics diagnostics when using the programmatic capture API, just run or debug your application.


The Graphics Experiment window


After a frame is captured, it is displayed in Visual Studio as Graphics Experiment.vsglog. Each captured frame will be added to the Frame List and is presented as a thumbnail image at the bottom of the Graphics Experiment tab. This logfile contains all the information needed for debugging and tracing. As you can see in the following screenshot, there are three subwindows: the left one displays the captured frames, the right one, which is named Graphics Events List, demonstrates the list of all DirectX events, and finally, the Graphics Pixel History subwindow in the middle is responsible for displaying the activities of the selected pixel in the running frame:


4803_04_08.png


Let's start with the Graphics Pixel History subwindow. As you can see in the preceding screenshot, we selected one of the pixels on the spaceship model. Now let us take a look at the Graphics Pixel History subwindow of that pixel, as shown in the following screenshot:


4803_04_09.png


The preceding screenshot shows how this pixel has been modified by each DirectX event; first it is initialized with a specific color, then it is changed to blue by the ClearRenderTargetView function and after this, it is changed to the color of our model by drawing indexed primitives. Open the collapsed DrawIndexed function to see what really happens in the Vertex Shader and Pixel Shader pipelines. The following screenshot shows the information about each of the vertices:


4803_04_10.png


The input layout of the vertex buffer is VertexPositionNormalTangentColorTexture. In this article, you can see each vertex's value of the model's triangle. Now, we would like to debug the Pixel Shader of this pixel, so just press the green triangular icon to start debugging. As you can see in the following screenshot, when the debug process is started, Visual Studio will navigate to the source code of the Pixel Shader:


4803_04_11.png


Now you can easily debug the Pixel Shader code of the specific pixel in the DrawIndexed stage. You can also right-click on each pixel of the captured frame and select Graphics Object Table to check the Direct3D object's data.


Following screenshot shows the Graphics Event List subwindow. Draw calls in this list are typically more important events:


4803_04_12.png


The event that is displayed with the 4803_04_13.png icon marks a draw event, the one that is displayed with the 4803_04_14.png icon marks an event that occurs before the captured frame, and the user-defined event marker or the group shown with the 4803_04_15.png icon can be defined inside the application code. In this example, we mark an event (Start rendering the model) before rendering the model and mark another event (The model has been rendered) after the model is rendered. You can create these events by using the ID3DUserDefinedAnnotation:: BeginEvent, ID3DUserDefinedAnnotation:: EndEvent, and ID3DUserDefinedAnnotation:: SetMarker interfaces.


Investigating a missing object


This section will demonstrate how to investigate an error that occurs in the Vertex Shader stage so we can find out why an object is missing from our scene. First we need to find the draw call for the missing geometry. Select the Draw Event from the event list then select Graphics Pipeline Stage by navigating to Debug | Graphics | Pipeline Stages.


First select Input Assembler and view the object's geometry inside the Model Editor. If we make sure that it is the missing geometry, we must find out why it is not shown in our scene. The following are some of the common reasons:
  • Incompatible input layout: When you click on the DrawIndexed event, you will see a window that shows the device context information. Make sure the input element description of the vertex buffer is the same as the Vertex Shader input structure.
  • Error in the Vertex Shader code: The input assembler provides the right data for the Vertex Shader, so we need to see what is going on inside the Vertex Shader. Use the HLSL debugger to examine the state of variables in the Vertex Shader, especially for the output position of the Vertex Shader.
  • Error in the application source code: If neither of the previous two solutions solve the rendering issue, then we must find out where the constant buffer is being set in the source code of the program. Open the Graphics Event Call Stack window by navigating to Debug | Graphics | Event Call Stack and check the source code.
  • Let's check the device state: Open the Graphics Object Table tool and examine the device state, especially the depth stencil view, to see whether the primitives have failed the depth test or not.

Summary


In this article, you have learned DirectX graphics diagnostic, how to capture the frame, and the graphics Experiment window.

Further reading


If you are interested to explore DirectX 11.1 in more detail, check DirectX 11.1 Game Programming book by Pooya Eimandar, published by PackT publishing.

Implementing Component-Entity-Systems

$
0
0
This is the follow-up to my article from April, Understanding Component-Entity-Systems. If you haven't read that article yet, I suggest looking it over because it explains the theory behind what I am about to show you.

To summarize what was written:
  • Components represent the data a game object can have
  • Entities represent a game object as an aggregation of components
  • Systems provide the logic to operate on components and simulate the game
The purpose of this article is to show how to implement the architecture that I described in an efficient way, and to provide solutions for some sample problems. All the code samples that I provide will be written in C.

Implementation


Components


I wrote in the last article that a component is essentially a C struct: plain old data, so that's what I used to implement them. They're pretty self-explanatory. I'll implement three types of component here:

  1. Displacement (x, y)
  2. Velocity (x, y)
  3. Appearance (name)

Here's the sample of code defining the Displacement component. It is a simple struct with two members that define its vector components.

typedef struct
{
	float x;
	float y;
} Displacement;

The Velocity component is defined the same way, and the Appearance has a single string member.

In addition to the concrete data types, the implementation makes use of an enum for creating "component masks", or bit fields, that identify a set of components. Each entity and system has a component mask, the use of which will be explained shortly.

typedef enum
{
	COMPONENT_NONE = 0,
	COMPONENT_DISPLACEMENT = 1 << 0,
	COMPONENT_VELOCITY = 1 << 1,
	COMPONENT_APPEARANCE = 1 << 2
} Component;

Defining a component mask is easy. In the context of an entity, a component mask describes which components the entity has. If the entity has a Displacement and a Appearance, the value of its component mask will be COMPONENT_DISPLACEMENT | COMPONENT_APPEARANCE.

Entities


The entity itself will not be defined as a concrete data type. In accordance with data-oriented-design (DOD) principles, having each entity be a structure containing each of its components, creating an "array of structs", is a no-no. Therefore, each component type will be laid out contiguously in memory, creating a "struct of arrays". This will improve cache coherency and facilitate iteration. In order to do this, the entity will be represented by an index into each component array. The component found at that index is considered as part of that entity.

I call this "struct of arrays" the World. Along with the components themselves, it stores a component mask for each entity.

typedef struct
{
	int mask[ENTITY_COUNT];

	Displacement displacement[ENTITY_COUNT];
	Velocity velocity[ENTITY_COUNT];
	Appearance appearance[ENTITY_COUNT];
} World;

ENTITY_COUNT is defined in my test program to be 100, but in a real game it will likely be much higher. In this implementation, the maximum number of entities is constrained to this value. I prefer to use stack-allocated memory to dynamic memory, but the world could also be implemented as a number of C++-style vectors, one per component.

Along with this structure, I have defined a couple of functions that are able to create and destroy specific entities.

unsigned int createEntity(World *world)
{
	unsigned int entity;
	for(entity = 0; entity < ENTITY_COUNT; ++entity)
	{
		if(world->mask[entity] == COMPONENT_NONE)
		{
			return(entity);
		}
	}

	printf("Error!  No more entities left!\n");
	return(ENTITY_COUNT);
}

void destroyEntity(World *world, unsigned int entity)
{
	world->mask[entity] = COMPONENT_NONE;
}

The first does not "create" an entity per se, but instead returns the first "empty" entity index, i.e. for the first entity with no components. The second simply sets an entity's component mask to nothing. Treating an entity with an empty component mask as "non-existent" is very intuitive, because no systems will run on it.

I've also created a few helper functions to create a fully-formed entity from initial parameters such as displacement and velocity. Here is the one that creates a tree, which has a Displacement and an Appearance.

unsigned int createTree(World *world, float x, float y)
{
	unsigned int entity = createEntity(world);

	world->mask[entity] = COMPONENT_DISPLACEMENT | COMPONENT_APPEARANCE;

	world->displacement[entity].x = x;
	world->displacement[entity].y = y;

	world->appearance[entity].name = "Tree";

	return(entity);
}

In a real-world engine, your entities would likely be defined using external data files, but that is beyond the scope of my test program. Even so, it is easy to see how flexible the entity creation system is.

Systems


The systems are easily the most complex part of the implementation. Each system is a generic function which is mapped to a certain component mask. This is the second use of a component mask: to define which components a certain system operates on.

#define MOVEMENT_MASK (COMPONENT_DISPLACEMENT | COMPONENT_VELOCITY)

void movementFunction(World *world)
{
	unsigned int entity;
	Displacement *d;
	Velocity *v;

	for(entity = 0; entity < ENTITY_COUNT; ++entity)
	{
		if((world->mask[entity] & MOVEMENT_MASK) == MOVEMENT_MASK)
		{
			d = &(world->displacement[entity]);
			v = &(world->velocity[entity]);

			v->y -= 0.98f;

			d->x += v->x;
			d->y += v->y;
		}
	}
}

Here is where the component mask becomes really powerful. It makes it trivial to select an entity based on whether or not it has certain components, and it does it quickly. If each entity was a concrete structure with a dictionary or set to show which components it has, it would be a much slower operation.

The system itself adds the effect of gravity and then moves any entity with both a Displacement and a Velocity. If all entities are initialized properly, every entity processed by this function is guaranteed to have a valid Displacement and Velocity.

The one downside of the component mask is that the number of possible components is finite. In this implementation it is 32 because the default integer type is 32 bits long. C++ provides the std::bitset<n> class, which is N bits long, and I'm sure other languages provide similar facilities. In C, the number of bits can be extended by using multiple component masks in an array and checking each one independently, like this:

(EntityMask[0] & SystemMask[0]) == SystemMask[0] && (EntityMask[1] & SystemMask[1]) == SystemMask[1] // && ...

Source Files


I've zipped up the source code here. Main.c runs a sample program that creates three entities and runs each system once.

Attached File  CES.zip   2.41KB   63 downloads


Real-World Problems


This implementation works very well in the small scope of my program and can easily be extended to use more components and systems. It can also easily be extended to run in a main loop, and extended to read entities from data files with some work.

This section will tackle some problems with transferring gameplay mechanics and advanced features over to my implementation of CES.

Power-ups and collision filtering


This problem was pitched to me by Krohm in a comment on the original article. He was asking about gameplay-specific behaviours in general, but provided the specific example of a power-up that stopped collisions with a certain entity type.

Dynamic components to the rescue! Let's create a component, say GhostBehaviour, that has a list of qualifiers for determining which entities an object can pass through. For example, a list of component masks, or possibly material indices. Any component can be added or removed (technically, enabled or disabled) from any entity at any time, simply by changing that entity's component mask. When the player grabs the powerup, the GhostBehaviour component will be added. It could also have a built-in timer to automatically remove itself after a few seconds.

To actually disable the necessary collisions, the typical collision response in a physics engine can be exploited. In most physics engines, there is first a step to detect collisions and produce contacts, and then a step to actually apply the contact forces to each body. Let's say that each of those steps is implemented in a system, and that there is a component that keeps track of each entity's collision contacts (Collidable). To permit the desired behaviour, each contact should store the index of the other entity.

By injecting a system that operates on a GhostBehaviour and a Collidable in between the two physics steps, the contacts between the entities that should pass through each other can be deleted before they are acted upon by the physics engine. This will have the effect of a disabled collision. The same system can also disable the GhostBehaviour after a few seconds.

A similar approach can be used to perform a certain action upon collision. There could be a unique system for every action, or the same system could govern all the actions. In any case, the system would read the collision contacts to determine whether the entity collided with some other entity and then act accordingly. In fact, this would be needed to give the player the power-up in the first place.

The Big F***ing Spell - Destroying all monsters


Another problem I received was how to kill all monsters with a spell. Thanks to smorgasbord for this one!

The key to solving this one is that a system can be run somewhere outside of the top-level main loop, and that any entity that is a monster, according to CES rules, satisfies the same component mask. For example, every entity with both Health and AI is a monster, and this can be described with a component mask.

Remember how a system is just a function and a component mask? Let's define the "kill all monsters" spell as a system. The function, at its core, is destroyEntity, but could also create particle effects or play a sound. The component mask of the system can be COMPONENT_HEALTH | COMPONENT_AI.

In terms of actually casting the spell, I mentioned in the previous article that each entity can have one or more input components, which store boolean or real values that map to various inputs, including AI and networked players. Let's create a MagicInputComponent that has a boolean value that says when the entity should cast the spell, and an ID corresponding to the spell that should be cast.

Each spell has a unique ID, which is actually a key in a lookup table. This lookup table maps a spell ID to the function that "casts" that spell. In this case, the function would run the system..

Conclusion


Remember that this is just a sample implementation. It works for the test program, but is probably not ready for a full game. However, I hope that the design principles have been made clear and that it is easy to understand and implement in your own way in a different language.

If you have any more problems you'd like me to solve you can write a comment or send me a message. I'll update this article periodically with my solutions.

Keep in touch for the next article!

Article Update Log


29 September 2013 - Initial submission
2 October 2013 - Initial publication; simplified system code (thanks TiagoCosta!); clarified 2nd example (thanks BeerNutts!)]
21 October 2013 - Reflected code changes in the source archive

An Open Letter to Young Programmers

$
0
0
One of the awesome things about ArenaNet is that we run a programming internship program that actually does a phenomenal job of preparing people to work in the games industry. This is accomplished by focusing on three primary principles:
  • Everything you do will matter. There is no pointless busy-work, no useless coffee-and-bagel fetching type nonsense, and every project contributes directly to something that impacts the studio and/or the game as a whole.
  • Everything you do will be reviewed. We have at least one senior programmer per intern dedicated to helping make sure that the work is top-notch. This entails exhaustive code reviews and extended design/analysis discussions before a line of code is even written.
  • Whether we end up hiring you or not, we're committed to making sure you leave the program as a good hire. The program is not a guaranteed-hire affair. However, with extremely few exceptions, we ensure that upon completion of the internship you're well prepared and ready to tackle game industry jobs.
One of the interns going through the program right now is assigned to me, and I've found it an awesome opportunity not just to mentor a more junior developer, but to force myself to crystallize and refine my own thinking so I can communicate it clearly to someone who doesn't have the benefit of many years of experience on the job.

Last week I had the chance to write down some thoughts about his performance so far in the program, and offer some feedback. After sitting down to re-read my letter, it struck me that there's a lot of stuff in there that might be useful for anyone who is just learning to work on large projects with large teams.

You may not be working on the next great MMO (or maybe you are!) but I think there's some value in this advice for anyone who is early on in their programming career.

Think Twice, Commit Once


This is my variant of the old carpenter's rule of "measure twice, cut once." In general, one of the biggest challenges of working on large-scale projects is keeping in mind all the ramifications of your decisions. Sometimes those implications are easier to see, and sometimes there's just no way to know ahead of time. But either way, it pays to take some time when writing code (and after writing code) to think very carefully about it.

One thing I personally like to do is let my non-trivial changelists sit for a day or so and then come back to them with a fresh mind, and re-read the code. I try to approach it as if I'd never seen the code before and was responsible for a code review on it. There are two directions that need to be considered: the tiny details, and the big-picture implications. Usually I will do two passes, one for each frame of mind.

I'll cover some of the small details later; the big things are generally harder anyways. It takes a lot of practice and experience to intuitively spot the consequences of design decisions; this has two important facets. First, it means that it won't be immediately obvious most of the time when you make a decision that has large-scale effects. Second, it means that it will take effort and conscious deliberation to train yourself to recognize those situations. The best suggestion I can offer is to pause often and try and envision the future - which I'll tackle next

Be Nice to Future You


I could also say "be nice to everyone else who will ever read your code" but that doesn't make for as nice of a section heading. The general idea here is that code is written once and then lives a very, very, very long time. The natural impact of this is that people will have to read the code many times while it survives. Again there are two directions this can go in: tiny details, and large-scale impacts, and again, the details are easier to spot - especially at first.

Some concrete examples are probably in order at this point. For details, one of the things that goes a long way is simple formatting. It may seem almost overbearingly anal-retentive to complain about whitespace and which line your braces go on, but it has an impact. After twenty-odd years of reading code, you get to a point where you can recognize common patterns very easily. Especially in a workplace like ArenaNet with a strict and consistently-followed formatting standard, this is a major time-saver; if everyone writes code that looks similar, it's easier to spot "weird" things. If my brain is distracted while reading a piece of code by the formatting, it gets harder to focus on the meaning and intent of the code itself.

On the larger scale, there are things like comments. Code commenting is a religious warfare issue in most of the world, and even ArenaNet has lots of diverse viewpoints on how it should be done. However, we have some basic philosophical common ground that is very helpful.

Sometimes comments need to go away, and sometimes they need to be added. Comments that need to go away are generally guilty of at least one of the following crimes:
  • Repeating what the code already says
  • Being out of date or in imminent danger of becoming out of date
  • Being outright false (often as a result of accidents with copy/paste)
Things that are good to comment are largely architectural: what modules do, how they fit together, what the intent of each major section of code is. The details ("this line of code adds this column to this CSV file") are already pretty obvious from the code itself - or at least, they should be, if the names used in the code are clear. Which leads to...

A Rose By Any Other Name Smells Like Shit


This is a lot more detail-oriented stuff. There are a number of conventions that any team will typically have that are important and help readability and clarity of intent.

There are many specific examples in ArenaNet's code standards documentation, but there are other conventions that are more general and likely to be in use in almost any environment. For example, pluralization is important. Iterator variables should be singular ("item" or "currency" or "character"). Containers should be plural ("items" or "currencies" or "characters"). The same goes for function names; if a function does one thing, name it singularly. If it does multiple things, or does one thing to multiple things, pluralize appropriately.

In general names are hard to select and should be chosen with great care. If a function's purpose changes, change its name as well. Always make sure a name corresponds to the best description of intent that you can manage. (Just don't use extremelyVerboseVariableNamesWithLotsOfExtraneousDetails.)

Wastefulness Will Bite You Sooner Rather Than Later


Extra parameters that aren't used by a function should be removed. (There are compiler warnings for this, by the way - you should always build with warnings as errors and the maximum warning level that you can manage.) Similarly, make sure that all variables are actually used and do something. Make sure that function calls are useful. For example, initializing a variable and then immediately changing its value just creates extra noise that confuses the reader. Passing nothing but a string literal to sprintf() and then printf()ing the resulting buffer is confusing as well, and a little wasteful.

This is partly about readability and partly about real efficiency. In large code bases like ours, the death isn't from a single massively wasteful piece of code - it's from thousands of tiny decisions that add up over time... both in terms of reading code, and in terms of how it performs at runtime. Both memory and processing power are something to keep in mind. They may seem cheap (or even free) at times, especially in environments with immense resources. But don't forget that everything has a cost and those costs accumulate a lot faster than we might wish sometimes.

A corollary to this is cleanup practices. If you remove a piece of functionality, make sure all vestiges of it are gone - comments, preparatory code, cleanup code, etc. It's easy to forget pieces of logic when removing things, and this just leads to more noise and confusion for the next reader. Once again, re-reading your own code with a critical eye helps a lot here.

Give It A Nice Home


Where things live in code as well as data is always an important consideration. Some things don't need to be wrapped into classes - if there's no state being carried around, or no shared interface that must be used, favor free functions instead of building classes that are just methods. On the data side, make sure to scope things as tightly as you can, and declare things as close as possible to their first point of use. Some things don't need to be file-level variables (let alone globals), and can just live as locals in some function. Some things don't even need to live the entire lifetime of a function. RAII is a big deal in C++, and should be used liberally.

File organization is also a big thing to keep in mind. Keeping related bits of code in self-contained files is a good habit; but it takes some careful thought to decide the taxonomy for what is "related." Think of programs like a pipeline. Each segment of pipe (module) should do one very specific and very contained thing. To build the whole program, you link together segments of pipe (modules/classes/etc.) and compose them into a more sophisticated machine.

Follow the Leader...


You should always try and emulate the code you're working in if someone else owns it. Follow the style, the naming patterns, the architectural decisions, and so on. Often you can make your life harder by departing from established convention, and you will definitely make the lives of everyone else harder at the same time.

Don't be shy to ask questions if any of those decisions or patterns are unclear. I know that a lot of this stuff seems a bit vague and mystical right now; much of the reasoning for why things are the way they are may not be immediately apparent. Asking questions is always good - if there are reasons for why things are a certain way, then you get to learn something; and if there are no reasons, or bad reasons, you open up an opportunity to make things better.

...But Clean Up What You Find


One of the highest aspirations we should have as team programmers is to leave code better than we found it. This ranges from fixing minor details to cleaning up design decisions and adding documentation. This is something you should try for even if you're not in your "own" code - often if there is objective room for improvement, the owner will actually appreciate you taking the time to make his area nicer. Obviously this doesn't mean you should go on a rampage to change every code file just for the sake of it, and there's always value in consulting with an owner before making changes - but you get the idea.

Learning Never Stops


It can be very tempting in life to plateau. Sometimes we just want to feel like we have "arrived" and now we "get it." And indeed there will be milestones in your career where you can notice a profound change in your skills and perspective.

The key is to never stop chasing the next boost. Even after more than twenty years of writing computer programs, I learn new things all the time. Your learning process is either constantly running, or you're effectively dead.

And that, of course, leads to the final piece of advice I could ever offer anyone on life: "don't be dead."

Building a 3D MMO using Websockets

$
0
0
Author's note: This article does not aim to build huge MMO's like World of Warcraft or similar. These games have millions of players and gigantic budgets, and employ many skilled engineers to keep servers from falling apart. Do not start one of these on your own. The techniques described in this article however allow you to build moderately-sized MMO's that can potentially hold hundreds of players on a single server, depending on your hardware and packet size.

Hi there! My name is Nick Janssen, creator of Ironbane, a 3D MMO that uses WebGL and WebSockets. With this article I would like to give you a better insight in MMO’s and make you less afraid of the complexities involved in building one. From my experience I have found that people consider them very hard, while they are in fact quite easy to make using the web technologies of today.

An MMO? You can’t do that!


MMO's are cool. Yet they are considered one of the hardest things to make when it comes to developing software. I believe MMO’s are mainly intimidating to people because of historical reasons.

In the old days, network programming used to be very hard. Complex socket calls were everywhere, multithreading was necessary and JSON was still unknown. A lot has changed since then with the coming of Node.js, its event loop and easy to use socket libraries.

In addition, writing a 3D game was a challenge on its own. You had to include the right libraries, install dependencies on the client, and write complicated engine calls to do trivial things such as creating a texture. Getting a triangle to show on the screen was already quite an accomplishment.

Creating a texture with DirectX10

D3DX10_IMAGE_LOAD_INFO loadInfo;
ZeroMemory( &loadInfo, sizeof(D3DX10_IMAGE_LOAD_INFO) );
loadInfo.BindFlags = D3D10_BIND_SHADER_RESOURCE;

ID3D10Resource *pTexture = NULL;
D3DX10CreateTextureFromFile( pDevice, "crate.gif", &loadInfo, NULL, &pTexture, NULL );

Creating a texture with Three.JS

var texture = THREE.ImageUtils.loadTexture('crate.gif'),

The beginnings


For our MMO Ironbane I took things one at a time, and it worked out very well. Remember, Rome wasn’t built in a day. But with today’s technology you can achieve things at a much faster pace than what was ever possible before.

I started from a three.js terrain demo and modified it step by step. Within a few days, I had a plane running around with a texture that looked like the pixelated back of a guy.

is1.png

The next step was to make the player connect to a centralized server. Using Socket.IO I set up a very simple Node.js backend that responds to player connections, and puts them in a global unitList managed by a service called worldHandler:

io.sockets.on("connection", function (socket) {
  socket.unit = null;
  socket.on("connectServer", function (data, reply) {
      var unit = new IB.Player(data);

      worldHandler.addUnit(unit);
  }
});

Telling players about other players nearby


To let players know which other players are nearby, the server has to know at any time which players can see other players. To do so, every player instance on the server makes use of an otherUnits array. This array is simply filled with instances of other entities which are currently in the vicinity.

When a new player is added to the worldHandler, their otherUnits list gets updated depending on where they are in the world. Later, when they move around, this list is evaluated again, and any changes to this list are sent to the client in the form of addUnit and removeUnit socket events.

Now, I would like to point out that the first letter of MMO stands for Massive. For massive games, every player should not know about every other player because it will melt your server.

Spatial partioning


To remedy this, you need spatial partioning. In a nutshell, this means that you divide your world into a grid. To visualize it, think of it as the server making use of a Snap To Grid option, to “snap” the position of the players to an imaginary grid. The positions of the players are not altered, rather the server just calculates what the new snapped position of the player would be.

8WyeWxR.png
Image courtesy of Gameprogrammingpatterns.com

With many players spanning over many different positions, some will have the same “snapped” position. A player then, should only know about all players that are snapped in the same position and all players that are just one cell away from them. You can easily convert between grid and world positions using these functions:

function worldToGridCoordinates(x, y, gridsize) {
  if ( gridsize % 2 != 0 ) console.error("gridsize not dividable by 2!");

  var gridHalf = gridsize / 2;

  x = Math.floor((x + gridHalf)/gridsize);
  y = Math.floor((y + gridHalf)/gridsize);

  return {
    x: x,
    y: y
  };
}

function gridToWorldCoordinates(x, y, gridsize) {
  if ( gridsize % 2 != 0 ) console.error("gridsize not dividable by 2!");

  x = (x * gridsize);
  y = (y * gridsize);

  return {
    x: x,
    y: y
  };
}

When a new player is created on the server, they automatically add themselves to a multidimensional array of units on the worldHandler, using the grid position. In Ironbane we even use an additional zone index, since most MMO’s have multiple areas where players can reside.

worldHandler.world[this.zone][this.cellX][this.cellY].units.push(this);

Updating the list of players nearby


Once they are added to the list of units on the server, the next step is calculate which other players are nearby.

// We have two lists
// There is a list of units we currently have, and a list that we will have once we recalculate
// If an item is in the first list, but no longer in the second list, do removeOtherUnit
// If an item is in the first & second list, don't do anything
// If an item is only in the last list, do addOtherUnit
var firstList = this.otherUnits;
var secondList = [];

// Check for all players that are nearby and add them to secondList
var gridPosition = worldToGridPosition(this.x, this.y, 50)

var cx = gridPosition.x;
var cy = gridPosition.y;

for (var x = cx - 1; x <= cx + 1; x++) {
    for (var y = cy - 1; y <= cy + 1; y++) {

        _.each(worldHandler.units[this.zone][x][y], function(unit) {
            if (unit !== this) {
                secondList.push(unit);
            }
        }, this);

    }
}

for (var i = 0; i < firstList.length; i++) {
    if (secondList.indexOf(firstList[i]) === -1) {
        // Not found in the second list, so remove it
        this.removeOtherUnit(firstList[i]);
    }
}
for (var i = 0; i < secondList.length; i++) {
    if (firstList.indexOf(secondList[i]) === -1) {
        // Not found in the first list, so add it
        this.addOtherUnit(secondList[i]);
    }
}

Here, addOtherUnit() adds that player to their otherUnits array, and sends a packet to the client informing a new player has entered in their vicinity. This packet will contain the initial position, velocity, name and other metadata which only needs to be sent once. removeOtherUnit() simply removes the player from their array, and tells the client to destroy that player.

var packet = {
    id: id,
    position: unit.position,
    name: unit.name,
    isGameMaster: true
};

this.socket.emit("addUnit", packet);

Sending packets to the players


Now, we have the beating heart of an MMO. The final step is to inform the players on a regular basis the positions of the other players in their vicinity. We do this step only twice per second, because we do not want to overload the server.

_.each(this.world, function(zone) {
    _.each(zone, function(cellX) {
        _.each(cellX, function(cellY) {
            _.each(cellY.units, function(unit) {

                var snapshot = [];

                _.each(unit.otherUnits, function(otherUnit) {
                    var packet = {
                        id:otherUnit.id,
                        x:otherUnit.x,
                        y:otherUnit.y
                    };

                    snapshot.push(packet);
                ));
            
                if ( snapshot.length ) {
                    unit.socket.emit("snapshot", snapshot);    
                }
                
            ));
        });
    });
}); 

Conclusion


That’s really all there is to building an MMO. The only things left to do now are building the features that are unique to your game, fine-tuning and security.

I hope I have given you fresh insights into MMO programming, and above all the courage to start working on them. At Ironbane we are surely looking for collaborators! You can find the full source code of Ironbane straight on GitHub, and you should be able to install it on your machine with ease.

Generating 2D Navmeshes

$
0
0
The idea behind navmeshes is simple: Instead of building a graph of waypoints to represent the paths an actor can take to get from one place to another, we build a mesh that represents the area where an actor is allowed to walk. Since I'm quite happy with the way my implementation turned out, here is a quick description of how I generate my navmeshes from the level geometry in 2D using Clipper and Poly2Tri.

Define potentially walkable areas


First, we build a list of polygons of all the areas where an actor could potentially walk. In order for Clipper to properly merge them later, the polygons should overlap somewhat. In my case, floor tiles are always rectangular, so I simply enlarged their polygons by one (the smallest possible value since Clipper uses integer coordinates) in each direction.


Attached Image: Slide1.PNG


Merge walkable polygons


We can then use Clipper to perform a union operation on the list to merge any overlapping polygons. Make sure to use NonZero as the PolyFillType for the operation. This tells Clipper to create polygons that are all wound the same way - counter-clockwise in my case. Using the default value (EvenOdd) may result in polygons that are wound the other way to represent holes.

clipper.AddPolygons(walkablePolygons, PolyType.Clip)
result := empty list
clipper.Execute(ClipType.Union, result, PolyFillType.NonZero, PolyFillType.NonZero)


Attached Image: Slide2.PNG


Define blocked areas


Now we need a list of polygons to represent the areas that are blocked by walls or any other entities. To generate each entity's polygon, take the vertices representing its 3D bounds (a bounding box, collision model or even the actual model), transform them to world space, project them onto the floor and construct their convex hull. The gift wrapping algorithm was fast enough for me and is easy to implement, but you may need a more sophisticated algorithm if your models have a significant number of vertices. Since I have very few entities that actually need a concave 2D outline, I decided that a convex hull is accurate enough in my case.

blockedPolygons := empty list
for each entity e:
	vertices := 3D bounds of e
	worldVertices := for each v in vertices => transform(v, e.WorldMatrix)
	floorVertices := for each v in worldVertices => [v.x, 0, v.z]
	hull := convexHull(floorVertices)
	add hull to blockedPolygons


Attached Image: Slide3.PNG


Expand polygons to account for actor size


Since every point within the mesh should be a valid position for an actor, we need to expand the blocked areas to account for the size of the actors. Luckily, Clipper provides a very handy OffsetPolygon function to do just that. The method's JoinType parameter controls the number of vertices that Clipper inserts at acute angles. I found that using Square to chamfer sharp corners without inflating the vertex count too much produces nice results.

for each polygon p in blockedPolygons:
	p := Clipper.OffsetPolygons(p, AMOUNT, JointType.Square)


Attached Image: Slide4.PNG


Subtract blocked areas from walkable areas


Now that we have two sets of polygons, we can use Clipper to subtract the blocked areas from the walkable areas. The resulting list contains the modified walkable polygons as well as blocked polygons that are fully contained within a walkable polygon. These holes can be identified by their clockwise winding order. After this step, Clipper's work is done and we need to convert the polygons to Poly2Tri's representation.

result := walkablePolygons
for each polygon p in blockedPolygons:
	clipper := new clipper
	clipper.AddPolygons(result, PolyType.Subject)
	clipper.AddPolygon(p, PolyType.Clip)
	clear result
	clipper.Execute(ClipType.Difference, result)
return result


Attached Image: Slide5.PNG


Optimize holes


Since we used the 3D bounds to generate the convex hulls, it is very likely that the polygons contain points that are in close proximity to each other. Since these points could lead to very long but thin triangles in the final mesh, it is wise to remove them before triangulating the polygons.

Tessellate polygons


Having a mixture of very big and very small triangles in the final mesh makes it harder for a pathfinding algorithm to estimate the distances between nodes. By repeatedly subdividing the sides of a polygon until a desirable length has been reached, we can limit the maximum size of the triangles in the final mesh. Placing Steiner points allows even more control over the final shape of the triangles. These points can be placed at arbitrary positions within a polygon to subdivide it without altering its shape. In my case, with obstacles that are seldom smaller than one square meter/unit, I found that placing Steiner points in a grid every two meters/units produces nice results.

Triangulate


The polygons are now ready to be triangulated by Poly2Tri. While Clipper saves all polygons in a flat list and identifies holes by their opposite winding order, Poly2Tri works best when every walkable polygon is assigned a list of the holes it contains.

walkablePolygons := all polygons with counter-clockwise winding order
blockedPolygons := all polygons with clockwise winding order
triangleList := empty
for each w in walkablePolygons:
	w.Holes := every polygon b in blockedPolygons that is contained in w
	triangulate(w)
	add every triangle in w.Triangles to triangleList
return triangleList

Triangulated mesh without and with additional Steiner points:


Attached Image: Slide6.PNG

Attached Image: Slide7.PNG


Find neighbours


We now have a "triangle soup". However, in order to use it for pathfinding, each triangle needs to know about its neighbours.

triangleIDs := empty map from polygon to int
vertexIDs := empty map from vertex position to int
vertices := empty list

// give every triangle and vertex a unique ID
for each triangle t in triangleSoup:
	triangleIDs[t] := triangleIDs.Count
	for each vertex p in t:
		if p not in vertexIDs:
			vertexIDs[p] = vertexIDs.Count
			add p to vertices
			
edge { V1, V2 } // undirected edge, edge1 is equal to edge2 if they contain the same vertices

// group all triangles that share an edge
edgeToTriangles := empty map from edge to list of triangles
for each triangle t in triangleSoup:
	edges := list of the three edges of t
	for each edge e in edges:
		add t to edgeToTriangles[e]

// gather each triangle's neighbours
for each triangle t in triangleSoup:
	myID := triangleIDs[t]
	edges := list of the three edges of t
	
	for each edge e in edges:
		for each triangle n in edgeToTriangles[e]:
			add n to t.Neighbours

We now have a list of triangles that we can use with A* or other pathfinding algorithms.

Telling an Immersive and Full Story with Mobile App Games

$
0
0
If there is ever any one thing that gets marginalized or passed over with many games, it can be the story actually. For the most part only games with RPG elements are considered to require a good story developed into it. And while you don’t always need some grand adventure-like story in every single game, especially ones like small mobile app games, you can still tell a story in subtle methods with those mobile app games, which can really get people invested more into the game and improve it across the board. But story-telling is no easy practice, it takes some time to learn and craft.

Why A Story for Mobile Games?


Games are a perfect platform for telling stories through a special interactive factor. And it’s not just the game developers that can tell a story, but the gamers as well, through games like Minecraft or Sims. They offer the ability to describe and depict a world all at once. But most of all, the reason you want a story in your game, a real developed story, is for human connection.

People always feel more connected to the characters you use in the game when you create a story around them. Even if it’s a small one like the one you see through pictures on the screen in between levels of Angry Birds. That story-telling aspect took little, and did plenty to explain the motives and adventures of the birds saving their young. Yes, no one will say that the story of Angry birds is why they played the game, but there is no doubt that being able to connect with your game's ‘protagonist’ did have an impact on how successful the game was.

And that’s one of the reasons story-telling for games are held down. If a story completely makes the game, it’s mentioned, but if the story only helped with combining the rest of the game, then the story wasn’t even considered as something that assisted the game in getting to a high tier. But there are clear indicators here that at the very least a well-rounded game, including story, almost always does better than others.

The Subtle Story-telling


So when it comes to the crafting of a story for a mobile game though, clearly you can’t exactly do the same thing many major games will do with cut scenes and extensive narratives going on. This is why mobile games by and large can often be considered the equivalent of a short story or flash fiction from the fiction writing world; where longer games like on consoles and pc would fall more into the category of novels or novellas.
This also means mobile games can draw from short stories for a direction on creating an immersive story without quite as much to work with.

The Theme or Character


When creating the story of your mobile game, focus directly on one particular theme or character. You could have a theme as simple as plants defending against zombies and go from there, or you could have a character, such as a vampire slayer which leads into the rest of the game and story. Regardless you should focus on one and don’t stray from that.

Detail Focused


With the theme covered you then have to mold the rest of the story, and it comes with a few ideas, but the most crucial is that you should be incredibly focused in the sense of preciseness or detail driven almost. This doesn’t mean to include so many details you bog the game down, but to think about the little things, because those are what will matter when building the world your game is in. And those little things are much easier to show with a mobile game, so give details, but leave the general idea alone, let people fill in their own ideas for what might happen in the story too.

Desires and Conflicts


These are the two things that will tie in the most with your game, between story and everything else, other than the theme of the game of course. The desires of the character are important to show and can almost always decide the type of mechanics you run with. Is your game a puzzle game because your character has to make sure they get all the right pills in the correct vial?

Additionally your conflict needs to be something that is short and although not solved immediately or easily, it should be solved soon. Mobile games aren’t incredibly long, even very story driven ones, and that means the solution to the conflicts should be something solvable within that limited amount of time. To this degree think of what a twenty-minute TV show does with telling a story compared to an hour and a half movie. Neither story is 'less', the problem or conflict is just resolved sooner in the TV show (or carried into multiple episodes).

As a final note to assist people with their story-telling in a mobile app game, don’t forget to play on people’s senses. You have sight with people looking at it, but draw on the sounds, cringe factor and smells. They can make a huge difference in the showing and showy aspect of your story-telling. Plus they often stand out more, since not everyone draws on how something smells or feels.


Article image credit BCcreativity

How the Procedural Map Generation Works in Reactor Heart

$
0
0
This article sums up what I've learnt so far developing the procedural map generation system of Reactor Heart. What I expose here should not be considered the last word on the topic since I'm still looking for the way to enhance the system. In the beginning, I made lots of rookie-mistakes and learnt a lot overcoming them; I hope it proves useful for you and If anyone knows, I'll gladly accept any suggestion to make my system better.

When I started developing Reactor Heart I realised immediately that I had to create an infinite universe able to automatically expand itself. With a Restricted world I wouldn't have made so far so I started my research gathering information on many articles and white papers about procedural generation.

The first idea I came up with was create a giant sector generator to allocate planetary systems, asteroids, and many other things. So I began to code function to create the planetary system basic elements: planets, asteroids, moons, minor satellites, etc. The planets were circles with a variable length ratio, the moons were the same but much smaller and for the asteroids I used an algorithm to create an outline with a logical and more or less round shape.

PlanetWithMoon.gif Asteroid.gif AsteroidPlanet.gif


With those elements I defined the soon to be planetary system generation in which planets were located in determined orbits round a centre (where I'd set a Star). They were defined with a random angle and distance from the center, restricted within reasonable ranges to set them coherently.

 Generation.gif


The generation system worked as expected with the exception of four issues:
  • The whole process lasted 15 seconds
  • The shapes of the asteriods didn't match what I had in mind.
  • The generation of each asteroid took a lot of time using the algorithm I designed.
  • Due to the random location of the asteroid, lots of blank spaces might occur.
For a limited map, 15 seconds are trifle since the generation is done once. For limitless universe where the users are expected to travel for hours that's unacceptable. Moreover, the asteroid's shape was kind of rhomboid and the system needed a lot of time to generate them.

The first issue I mended was the asteroids, which was the most obvious one. I created an asteroids editor in which I could pre-design a set of asteroids and save them to load them later in the game. Without the asteroids generation equation, the loading time improved enormously. It did even better when I added pre-processing while exporting the files in the editor. In addition, the asteroids shape were more coherent and, when combined, bigger asteroids with cool shapes were generated.

Despite fixing the asteroids issue, the loading time was still not good so I had two possibilities:
  • Work on revamping the algorithm by means of parallelism.
  • Lessen the generation system complexity.
In systems where Real Time generation was mandatory, they reduced the “unit” size and hence, reducing the amount time necessary for the generation. At first, my “unit” was the sector size and to follow this philosophy I decided to make it smaller. The sector would be an area of space defined by 6x6 chunks (the planetary system was 50x50) that might be occupied by a planet, a planet with some moons or asteroids, a cloud of asteroids, a nebula, a Star, etc.

The system worked wonderfully and I managed to reduce the generation time to a mean of 0.007-0.1 seconds using this new method (and with some low-level optimizations in the process ).

Tip: The use of profiling tools like gprof is very helpful to seek those functions that do most of the calls and see which part of the code you should try to optimize.

SectorSystem.gif


I haven't parallelized the system yet though I've designed it with an eye to the future to make it easier for subsequent versions to integrate it.

And that has been my experience so far.
Viewing all 17825 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>