Storing MFD Instances

From OrbiterWiki
Revision as of 15:14, 6 May 2008 by Enjo (talk | contribs) (It's better to access a vector's element with at() instead of [], because at() can generate exceptions.)
Jump to navigation Jump to search

In Orbiter, if you switch focus to another vessel, or switch camera modes, the MFDs that are open get deleted. This is because MFDs are strictly designed for displaying data. But what if you were writing an MFD that needed to cache data? For example, a user defined value. Every time the user switches focus, or camera modes, the MFD will get destroyed. This article shows how you can bypass this problem. Moreover this approach allows every vessel to store its own data.

Basic Plan

What we will do is create a storage class that stores all of the MFD data, including a handle to the vessel to which it is linked. Then we will modify our MFD class so it contains a pointer to an MFDData object. Then in the global scope, we will create a vector of MFDData pointers. In the constructor of the MFD, we will go through the vector, and try to match the linking vessel with the vessel handle inside the MFDData instances. If we find a match, we will assign our MFDData pointer (the one inside our class) appropriately. Otherwise we will create an MFDData object, and push it onto the vector.

MFDData storage class

This class should store all of the MFDData that you want saved. It should be created above the MFD class, in the same file

class MFDData
{
public:
	MFDData()
	{
           value = 10;
	}
	OBJHANDLE hook;   // important, it must be here in order to link it back to the vessel it was created for 
        int       value;  // data I want to save
};

MFD class

This is our MFD class. It should contain a pointer to an MFDData object.

class RAMFD: public MFD 
{
public:
	RAMFD  (DWORD w, DWORD h, VESSEL *vessel);
	~RAMFD ();
	void          Update (HDC hDC);
	static int    MsgProc (UINT msg, UINT mfd, WPARAM wparam, LPARAM lparam);
	MFDData *     data; // our data
};

Declaring vector in global scope

This should be in the file which contains the RAMFD implementation.

#define  STRICT
#define  ORBITER_MODULE
#include <orbitersdk.h>
#include "RAMFD.h"
#include <vector>

std::vector<MFDData*> g_data; // contains pointers to all the MFDData instances

MFD Constructor

This is where we search the vector for a match of the linking vessels, if no match is found, we create an MFDData object and store a pointer into the vector.

RAMFD::RAMFD (DWORD w, DWORD h, VESSEL *vessel)
: MFD (w, h, vessel)
{
	bool found = false;
        // scanning vector
	for (int i = 0; i < g_data.size(); i++)
	{
                // match found!
		if (g_data.at(i)->hook==vessel->GetHandle())
		{
                        // cache the pointer
			this->data=g_data.at(i);
			found=true;
			break;
		}
	}
        // no match found, we create the data ourself
        // and store a pointer in the vector
	if (!found)
	{
		MFDData * d = new MFDData();
		d->hook=vessel->GetHandle(); // important, this is so we can identify it next time
		g_data.push_back(d);
		this->data=d;
	}
}

Accessing data

Now we can access our data through the pointer we are caching. Example:

void RAMFD::Update(HDC hDC)
{
   char cbuf[255];
   int  len = 0;
   
   len = sprintf(cbuf, "My Value: %d", data->value);
   TextOut(hDC, W/35, H/24*2, cbuf, len);
}

Keeping data valid

Overtime, the vector storing the MFDData instances may fill up with invalid instances. What if the linking vessel of the MFDData instance is deleted? If you don't check the pointer inside the MFDData instance, it will surely cause a CTD. So it's good to clean the vector BEFORE using it. I like to do this in opcPreStep():

void Prune()
{
    if (g_data.empty())
        return;
    std::vector<MFDData*>::iterator it = g_data.begin() + 1;
    for (int i = 0; i < g_data.size(); i++)
    {
        it = g_data.begin() + i;
        if (!oapiIsVessel(g_data.at(i)->hook)) // if vessel does not exist, delete the vector
        {
            delete g_data.at(i);
            g_data.erase(it);
        }
    }
}