Storing MFD Instances

From OrbiterWiki
Jump to navigation Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.

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. Based on the approach presented first, a library has been created, that hides all the implementation details, and provides a more secure and cleaner way of achieving the same goal. Additionally it allows to execute code independently of the created MFDs, like for guidance algorithms. The library is called MultipleVesselsMFD.

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 from the vector
        {
            delete g_data.at(i);
            g_data.erase(it);
        }
    }
}

MultipleVesselsMFD library

The library is available at SourceForge-->Code, and licensed as LGPL. This means that if you want link it with your code, you can link it dynamically, without any responsibilities, but if you link it statically, you have to LGPL or GPL your code. The library provides a very clean and random-CTD-at-vessel-deletion secure way of achieving the same goal as the above code, but requires to do a few things described here. The package contains Doxygen documentation. You should open it and keep it as a reference, to view specific classes that will be described. The necessary steps are the following:

  • Derive a class from MFDData to add any additional members and functionalities that you want your MFD to contain. In this example, it will be called MyMFDData.
  • Derive from PluginMultipleVessels, where the method ConstructNewMFDData should return a new MFDData derived object pointer (MyMFDData *). It will be stored and managed internally. In this example, the Plugin derived class will be called PluginMyMFD. For instance:
MFDData * PluginMyMFD::ConstructNewMFDData( VESSEL * vessel ) 
{ 
    return new MyMFDData( vessel ); 
}
  • Declare a global pointer to the derived Plugin class, initialize it and register it in Orbiter's InitModule() along with the usual MFD inits.
PluginMyMFD * pPluginMyMFD; 
 
// Called on module init 
DLLCLBK void InitModule (HINSTANCE hDLL) 
{ 
    // init spec and register MFD mode
    pPluginMyMFD = new PluginMyMFD(hDLL); 
    oapiRegisterModule (pPluginMyMFD);
} 
  • Declare a specific constructor that accepts the derived PluginMultipleVessels class and Declare the derived MFDData in the main MFD class, for instance:
class MyMFD : public MFD2
{
   public:
     /// Default constructor
   MyMFD( DWORD w, DWORD h, VESSEL * vessel, PluginMyMFD * pluginMyMFD );
     /// Returns MFDData
     MyMFDData * GetData() const;
     
   private:
     MyMFDData * m_data;
}
  • Pass the Plugin to MFD's constructor, initialize the MyMFDData and perform a simple check
// Constructor 
MyMFD::MyMFD(DWORD w, DWORD h, VESSEL *vessel, PluginMyMFD * pluginMyMFD ) 
// Initialisation list 
: MFD2 (w, h, vessel) 
/* init m_data with PluginMultipleVessels's return value, depending on the vessel pointer. If dynamic cast fails, the m_data member becomes NULL. */
, m_data(dynamic_cast<MyMFDData *>(pluginMyMFD->AssociateMFDData(vessel))) 
{ 
    if ( m_data == NULL ) // Programming error 
        sprintf_s(oapiDebugString(), 512, "m_data pointer type is not compatible with the pointer that was being assigned to it in Ctor"); 
}
  • Place all vessel state updates in MyMFDData::Update() and call it in MFD2::Update()
// Repaint the MFD 
bool MyMFD::Update ( oapi::Sketchpad * skp ) 
{ 
    if ( m_data == NULL ) 
        return false; 

    // Update all ship's variables 
    m_data->Update(); 
 
    // Draws the MFD title 
    Title (skp, "My Multiple Vessels MFD"); 
    PrintResults(skp); // Print data from m_data
 
    return true; 
}
  • If it's necessary, you can update each of the vessels' MFDData in your derived plugin's UpdatePreStep() (or UpdatePostStep() if needed) that runs even if the MFD itself is disabled and for all vessels simultaneously. Example usage would be autopilots.
void PluginMyMFD::UpdatePreStep( MFDData * data ) 
{ 
   // Normally you'd use dynamic_cast and check if the returned value is NULL, 
    // but this method is supposed to work fast enough. 
    MyMFDData * myMFDData = static_cast<MyMFDData *> (data); 
    AutopilotBase * apBase = m_apMan.GetAP(myMFDData->GetAutopilotType()); 
    if (apBase != NULL) 
    { 
      // Only now it's reasonable to call data->Update() not to waste resources in case it's actually not needed.
        myMFDData->Update(); 
        apBase->Guide(myMFDData); 
    } 
}

void PluginMyMFD::UpdatePostStep( MFDData * data ) 
{
  // Must be implemented
}

These were the minimal steps to use the library, but there is a working example in the library package.