News New release: multiple MFD button pages framework

Enjo

Mostly harmless
Addon Developer
Tutorial Publisher
Donator
Joined
Nov 25, 2007
Messages
1,667
Reaction score
19
Points
38
Location
Germany
Website
www.enderspace.de
Preferred Pronouns
Can't you smell my T levels?
I'd like to share a new small framework for creating and switching among multiple MFD page buttons, with little interaction with the internals. It assumes registering handlers in the MFD and calling them on typical Orbiter MFD events. Please comment before I release it more officially.

First an example usage in Launch MFD client class:

Header:
PHP:
#ifndef MFDBUTTONPAGELAUNCHMFD_H
#define MFDBUTTONPAGELAUNCHMFD_H

#include "MDFButtonPage.hpp"
#include "LaunchMFD.h"

class MFDButtonPageLaunchMFD : public MDFButtonPage<LaunchMFD>
{
    public:
        MFDButtonPageLaunchMFD();
    protected:
        bool SearchForKeysInOtherPages() const;
    private:
};

#endif // MFDBUTTONPAGELAUNCHMFD_H
Implementation
PHP:
#include "MFDButtonPageLaunchMFD.hpp"
#include "localisation.h"

MFDButtonPageLaunchMFD::MFDButtonPageLaunchMFD()
{
    // The menu descriptions of all buttons
    static const MFDBUTTONMENU mnu1[] =
    {
        {SELECT_TARGET, 0, 'T'},
        {ENTER_ALT, "km", 'A'},
        {INCREASE_INCLINATION, 0, '+'},
        {DECREASE_INCLINATION, 0, '-'},
        {INCREASE_INCLINATION_FACTOR, 0, ']'},
        {DECREASE_INCLINATION_FACTOR, 0, '['},

        {SWITCH_BUTTONS_PAGE, 0, 'B'},
        {OPERATION_MODE, 0, 'M'},
        {DEFAULT_ACTION, 0, 'D'},
        {SWITCH_PITCH_GUIDANCE, 0, 'I'},
        {OFF_PLANE_CORRECTION, 0, 'O'},
        {AUTOPILOT, 0, 'P'}
    };
    RegisterPage(mnu1, sizeof(mnu1) / sizeof(MFDBUTTONMENU));

    RegisterFunction("TGT", OAPI_KEY_T, &LaunchMFD::OpenDialogTarget);
    RegisterFunction("ALT", OAPI_KEY_A, &LaunchMFD::OpenDialogAltitude);
    RegisterFuncCont("I +", OAPI_KEY_EQUALS,    &LaunchMFD::IncreaseInclination,        &LaunchMFD::DecreaseInclination);
    RegisterFuncCont("I -", OAPI_KEY_MINUS,     &LaunchMFD::DecreaseInclination,        &LaunchMFD::IncreaseInclination);
    RegisterFunction("Ad+", OAPI_KEY_RBRACKET,  &LaunchMFD::IncreaseInclinationFactor,  &LaunchMFD::DecreaseInclinationFactor);
    RegisterFunction("Ad-", OAPI_KEY_LBRACKET,  &LaunchMFD::DecreaseInclinationFactor,  &LaunchMFD::IncreaseInclinationFactor);

    RegisterFunction("PG",  OAPI_KEY_B, &LaunchMFD::SwitchButtonsPage);
    RegisterFunction("MOD", OAPI_KEY_M, &LaunchMFD::SwitchMode);
    RegisterFunction("DEF", OAPI_KEY_D, &LaunchMFD::DefaultAction);
    RegisterFunction("PTC", OAPI_KEY_I, &LaunchMFD::SwitchPitchGuidance);
    RegisterFunction("COR", OAPI_KEY_O, &LaunchMFD::SwitchOffplaneCorrection);
    RegisterFunction("AP",  OAPI_KEY_P, &LaunchMFD::SwitchAutopilot);

    static const MFDBUTTONMENU mnu2[] =
    {
        {GREAT_CIRCLE_SWITCH, GREAT_CIRCLE, 'G'},
        {GREAT_CIRCLE_TRACK, 0, 'K'},
        {GREAT_CIRCLE_ZOOM_IN, 0, 'Z'},
        {GREAT_CIRCLE_ZOOM_OUT, 0, 'X'},
        {GREAT_CIRCLE_PREC_INCR, 0, 'C'},
        {GREAT_CIRCLE_PREC_DECR, 0, 'V'},

        {SWITCH_BUTTONS_PAGE, 0, 'B'},
        {SWITCH_HUD, 0, 'H'},
        {SWITCH_SOUNDS, 0, 'S'},
        {PID_ADJUST_XY, 0, '1'},
        {PID_ADJUST_BANK, 0, '2'},
    };
    RegisterPage(mnu2, sizeof(mnu2) / sizeof(MFDBUTTONMENU));

    RegisterFunction("GC",  OAPI_KEY_G, &LaunchMFD::SwitchGreatCircleUse);
    RegisterFunction("TRK", OAPI_KEY_K, &LaunchMFD::SwitchGreatCircleTrack);
    RegisterFuncCont("ZM+", OAPI_KEY_Z, &LaunchMFD::GreatCircleZoomIn);
    RegisterFuncCont("ZM-", OAPI_KEY_X, &LaunchMFD::GreatCircleZoomOut);
    RegisterFunction("PR+", OAPI_KEY_C, &LaunchMFD::GreatCircleIncreasePlotPrecision, &LaunchMFD::GreatCircleDecreasePlotPrecision);
    RegisterFunction("PR-", OAPI_KEY_V, &LaunchMFD::GreatCircleDecreasePlotPrecision, &LaunchMFD::GreatCircleIncreasePlotPrecision);

    RegisterFunction("PG",  OAPI_KEY_B, &LaunchMFD::SwitchButtonsPage);
    RegisterFunction("HUD", OAPI_KEY_H, &LaunchMFD::SwitchHUD);
    RegisterFunction("SND", OAPI_KEY_S, &LaunchMFD::SwitchSound);
    RegisterFunction("PXY", OAPI_KEY_1, &LaunchMFD::OpenDialogPIDXY);
    RegisterFunction("PBN", OAPI_KEY_2, &LaunchMFD::OpenDialogPIDBank);
}

bool MFDButtonPageLaunchMFD::SearchForKeysInOtherPages() const
{
    return true;
}
PHP:
// ...
class LaunchMFD: public MFD2
{
public:
   //....
    // handlers
    void SwitchButtonsPage();
    void SwitchMode();
    void SwitchAutopilot();
    void SwitchOffplaneCorrection();
    void SwitchGreatCircleUse();
    void SwitchGreatCircleTrack();
    void SwitchHUD();
    void SwitchSound();
    void GreatCircleZoomIn();
    void GreatCircleZoomOut();
    void IncreaseInclination();
    void DecreaseInclination();
    void IncreaseInclinationFactor();
    void DecreaseInclinationFactor();
    void DefaultAction();
    void OpenDialogTarget();
    void OpenDialogAltitude();
    void OpenDialogPIDXY();
    void OpenDialogPIDBank();
    void DoNothing(); // for empty buttons
// ...
PHP:
MFDButtonPageLaunchMFD  m_buttonPages; // in this example global for simplicity

char * LaunchMFD::ButtonLabel (int bt)
{
    return m_buttonPages.ButtonLabel(bt);
}

int LaunchMFD::ButtonMenu (const MFDBUTTONMENU **menu) const
{
    return m_buttonPages.ButtonMenu( menu );
}

bool LaunchMFD::ConsumeButton (int bt, int event)
{
    return m_buttonPages.ConsumeButton(this, bt, event);
}

bool LaunchMFD::ConsumeKeyBuffered(DWORD key)
{
    return m_buttonPages.ConsumeKeyBuffered(this, key);
}

bool LaunchMFD::ConsumeKeyImmediate( char * kstate )
{
    return m_buttonPages.ConsumeKeyImmediate(this, kstate);
}

// Event handlers
void LaunchMFD::SwitchButtonsPage() // This handler is mandatory
{
    m_buttonPages.SwitchPage(this);
}
void LaunchMFD::SwitchAutopilot()
{
    m_data->SwitchAutopilot( m_data->pageView == DIRECT_ASCENT ? AP_DIRECT_ASCENT : AP_STANDARD );
}
void LaunchMFD::SwitchPitchGuidance()
{
    m_data->drawPitchError = ! m_data->drawPitchError;
}
//...
The library documentation:
Interface
Implementation
 
Last edited:
Hi Enjo,

I would be interested in seeing a development of a general framework for MFD development. I think you have a good start here with the button handling, but I would also add the general capability for persisting data and control code for cross-MFD synchronization and persistence across reinstantiations of the main class (e.g. F8 key presses).

I would also like a general framework for scenario load / save, custom config file parsing, diagnostics (e.g. callback traces for each module called, in order, so you can debug things), and text & graphics handling.

The Glideslope 2 code framework has some of this implemented specifically, but it could be easily generalized.
 
Hi there,

Most of things you are talking about are already there, although some are in a less formal state.

... I would also add the general capability for persisting data and control code for cross-MFD synchronization and persistence across reinstantiations of the main class (e.g. F8 key presses).
MultipleVesselsMFD library. Documented in Launch MFD's docs. It doesn't allow for IMFD-like multiple instances of data per vessel though, only one.

...custom config file parsing
A stub of config file reading here.
...diagnostics (e.g. callback traces for each module called, in order, so you can debug things)
Standard C++ stack trace capability.
...and text & graphics handling.
Macros by Agentgonzo.

Additionally:
Base lib folder, base libMFD folder

The Glideslope 2 code framework has some of this implemented specifically, but it could be easily generalized.

Which ones exactly?


There are two problems however:
1) I don't have much time for this anymore
2) I've seen only one person using my MultipleVesselsMFD library, and only because I've spent time on convincing him to do it. Others rather doing everything on their own, resulting in addons with dirty and hard to maintain code </rant>
 
Last edited:
Hi there,

Most of things you are talking about are already there, although some are in a less formal state.


MultipleVesselsMFD library. Documented in Launch MFD's docs. It doesn't allow for IMFD-like multiple instances of data per vessel though, only one.

Oh wow ... you have just opened the lid on a treasure chest of new things to go examine. I am indebted to you, Enjo! The classes and math in LaunchMFD are immense. That's my next month taken up studying them (between real life interruptions, as usual).

Yes indeed - the Multiple Vessels MFD library was exactly what I was referring to. Somewhere to figure out persistence per MFD and per vessel, with ability to store globals for each of those dimensions (i.e. left screen default, or vessel default).


Which ones exactly?

I built some handlers for parsing tokens from config files (quoted strings, bools, etc), a set of engineering unit converters to format say 1.345GJ from a double, or to lock the unit as needed (e.g. 1345.000MJ). The base MFD data handling as you already have. And there's lots of original code from Chris Jepessen for the matrix and vector manipulation which may well have found its way into your code given I see Chris's name on your documentation.


There are two problems however:
1) I don't have much time for this anymore
2) I've seen only one person using my MultipleVesselsMFD library, and only because I've spent time on convincing him to do it. Others rather doing everything on their own, resulting in addons with dirty and hard to maintain code </rant>

I think it's simply a lack of awareness that this great work even exists.

In my view ... (not a rant, but things I wish I had)

1. There is not a good tutorial and documentation on MFD development, that talks about these issues.

2. I don't know where to go to find the details on the callback sequences.

3. I don't know where to go read to get a solid understanding of the vector and matrix math in these calculations (i.e. coordinate systems, transformations, dot-products, rotation matrices ... ok I know a bit, but a solid primer to make sure my first principles are solid).

4. I don't know how to debug these MFD's in a more sophisticated way then oapiDebugString and appending debug log files with trace data as its running. (I.e. no MFD test rig to let me run it fully in Visual Studio debug mode!).

5. I would love to see the material in Orbitersdk\doc\API_Reference.chm but I see the titles on the left navigation but no content. How to fix this?

6. How to approach modifying MFD-derived code to MFD2? Is this easy, or hard?

7. How to dynamically create a pop-up selection menu (e.g. Map MFD base selection)?

8. How to enable the MFD to write on the HUD (e.g. to put a guidance rectangle tunnel up like rendezvous MFD, or to float context data and warnings on the HUD as well as the MFD).


Overcoming some of these things has been a great learning experience and really part of the whole Orbiter addon developer experience that I have enjoyed, but it would be great to have a strong documented base of best practice for this up front. With this, I'm sure you would get more adoption - i.e. it would be the standard way to do this stuff and get more of what you want (making your own code work) with less recreation of the underlying framework. (My rant over too :))
 
5. I would love to see the material in Orbitersdk\doc\API_Reference.chm but I see the titles on the left navigation but no content. How to fix this?
For exactly this file there are 2 other alternatives with the same content:
  1. Orbitersdk\doc\API_Reference.pdf,
  2. Header files in Orbitersdk\include directory, exactly from which the API_Reference documents are generated (by Doxygen).

4. I don't know how to debug these MFD's in a more sophisticated way then oapiDebugString and appending debug log files with trace data as its running. (I.e. no MFD test rig to let me run it fully in Visual Studio debug mode!).
That's a totally different approach for debugging than mine. You should try breakpoints. I use almost exclusively breakpoints (normal or conditional) set in the code with addition of step through and step into debugging features, and I never use a file or the oapiDebugString function for debugging purposes.
 
I'm glad you like it. To understand better the Math package, I advice you to find the places where the functions are called. They lack documentation, I know. Also, some part of the code is not used anymore in LaunchMFD itself, but from the LegacyCode directory, which is a base for a program called da_solver (available at SourceForge). That's history though. The reality doesn't work this way.

And there's lots of original code from Chris Jepessen for the matrix and vector manipulation which may well have found its way into your code given I see Chris's name on your documentation.

I use Chris' PEG in LaunchMFD. Also for the Direct Ascent, which is really a clever usage of PEG in disguise.


I realize that lack of popularity is really a call for marketing, ie. writing a good documentation. Thanks for bringing up the points. Even better would be probably making additionally a project template(s) with the features already in-place.

However, I don't know about you, but because of my very poor life situation recently (lack of free time & money & stability), I've turned into Not-Caring-Anymore, Commuting/Working Zombie.

[EDIT]
Oh, just read your PM. Give me some time, as my holiday is over today.
 
Last edited:
For exactly this file there are 2 other alternatives with the same content:
  1. Orbitersdk\doc\API_Reference.pdf,
  2. Header files in Orbitersdk\include directory, exactly from which the API_Reference documents are generated (by Doxygen).


That's a totally different approach for debugging than mine. You should try breakpoints. I use almost exclusively breakpoints (normal or conditional) set in the code with addition of step through and step into debugging features, and I never use a file or the oapiDebugString function for debugging purposes.

Thanks Orb. I didn't know if the material was identical or not. Any reason why the .chm is not displaying content though?

On the breakpoints ... that's exactly what I would do for non-Orbiter work, and for example when I was debugging my config file parser code, I hooked up a test rig to run those classes outside of orbiter.

Is there a way to run Orbiter in a window, then run a debug DLL where you trap straight into the Visual Studio and leave Orbiter hanging whilst you step through the code? If so, can you give me a play-by-play how to do this?

Thanks guys - learning a lot this morning already!

---------- Post added at 06:04 PM ---------- Previous post was at 05:59 PM ----------

However, I don't know about you, but because of my very poor life situation recently (lack of free time & money & stability), I've turned into Not-Caring-Anymore, Commuting/Working Zombie.

I hear you, brother! Four times up before 5.30am and one at 4.30am last week for me, my fellow Commute/Work Zombie!. Hang in there!
 
Is there a way to run Orbiter in a window, then run a debug DLL where you trap straight into the Visual Studio and leave Orbiter hanging whilst you step through the code? If so, can you give me a play-by-play how to do this?
Have you ever launched Orbiter from inside Visual Studio IDE? > [General Question] Launching Orbiter after compilation

The debugger is attached to the Orbiter process after you do that, and that means breakpoints can be set in any .dll module/library it will load.

There is an option to run simulation in windowed mode on the Video tab of Orbiter Launchpad.

To set breakpoints just click on the left side of the lines in the source file view in Visual Studio IDE, before which you want to stop execution. You can do it either before launching Orbiter from Visual Studio (to debug plug-in modules), or if you are sure your code won't get executed before you launch scenario, you can do it after you start debugging (green "play" button or "start debugging" command in Debugging ment) and alt-tab back from the Orbiter's launch pad back to to Visual Studio to set breakpoints (e.g. in in-simulation functions of your code). The set breakpoints will be valid as soon as Orbiter will load your module compiled from that source code.
 
Gotta try this out now. (More work on in my Orbiter to-do inbox!)
 
From ADSWNJ #4 above:

5. I would love to see the material in Orbitersdk\doc\API_Reference.chm but I see the titles on the left navigation but no content. How to fix this?

If this is the same problem I have had in Vista, its fixed by right-clicking on the .chm file, selecting "Properties", then selecting the "Unblock" button on the bottom of the page.

N.
 
I've updated the library. The newest feature is the possibility to define a button which should have a continuous reaction - like TransX' or TransferMFD variable modifications. This is done through RegisterFuncCont. Also, there's a possibility to select a given buttons page, not just switching them (thanks to ADSWNJ for the idea)

If you look at the updated documentation html, you'll see that I'm nearing to build a library of common MFD functionalities.
(another new library is the MFDHUDDrawer)
 
Last edited:
Back
Top