SimObject API


Contents
Related Links

Overview

The SimObject API utilizes a service-based methodology for building simulation behaviors to be visualized in Prepar3D. The API enables a solution developer to create a simulation object (SimObject) complete with customized behaviors, input properties (also referred to as events or triggers), and state properties (also referred to as simvars or simply properties). These properties can be referenced in content such as SimObject gauges, animations, and scenario scripts which are discussed in more detail in other parts of the SDK. The properties are text-based, and can be referenced in the same way as the stock simvars and events are referred to in other parts of the Prepar3D SDK.


GettingStarted

A solution developer using this SDK should be familiar with the following:


SimObject Conventions and Standards

Prepar3D SimObjects must obey the following conventions to function correctly:


SimObject API Samples

The Prepar3D SDK comes with several samples that show the functionality of the ISimObject API. More information is available on the PDK Samples Overview page.


Creating a new solution and DLL using the SimObject API

Create new VC++ Project

Configuring your DLL

Creating a New SimObject Class

For starters, this can be as simple as the following example:

#include <atlcomcli.h>
#include <ISimObject.h>
#include <InitGuid.h>

class MySimObject : public P3D::ISimObject
{
      STDMETHOD (LoadConstantData)(__outvoid** ppConstantData)          override {return E_FAIL;}
      STDMETHOD (UnloadConstantData)(__inoutvoid** ppConstantData)      override {return E_FAIL;}
      STDMETHOD (LoadDynamicData)()                                     override {return E_FAIL;}
      STDMETHOD (Init)()                                                override {return E_FAIL;}
      STDMETHOD (DeInit)()                                              override {return E_FAIL;}

//IUnknown functions will be required here
};

static HRESULT New(__in __notnull P3D::IBaseObject* pBaseObject, __out __notnull P3D::ISimObject** ppThisSim)
{
    return E_FAIL;
}

DEFINE_GUID(CLSID_MySimObject, 0x2afb2ae8, 0xe74, 0x4d76, 0x8c, 0x6e, 0x7b, 0x5a, 0x42, 0xef, 0x19, 0x6e);

Registering Your SimObject Implementation

To register your SimObject class, you must first obtain a reference to the Prepar3D Developer Kit interface. The PDK API is a service provider for obtaining services to the Prepar3D platform. An interface to the PDK (IPdk) will be necessary to obtain the SimObject Manager in order to register a new SimObject implementation. Like the other interfaces in this SDK, the PDK is a reference-counted object so the reference must be released to avoid memory leaks. Smart pointers are also convenient for this purpose and are used extensively in the samples. Here is an example of how to register the above SimObject implementation:

Registration with the SimObject Manager object requires 3 elements. The unique GUID and static "factory" function were discussed in the previous section. In addition, a friendly category name must be specified. This is used for filtering objects of similar classes, such as "Airplane", "Helicopter", or "GroundVehicle". This name is also referenced in the Prepar3D.cfg [Main] User Objects setting to determine which object categories are selectable through the UI.

 #include <atlcomcli.h>
 #include <Pdk.h>
 #include <ISimObject.h>


void __stdcall DLLStart(P3D::IPdk* pPdk)
{
    P3D::ISimObjectManager*  pManager = NULL;
    
    HRESULT hr = E_FAIL;
    
    if (pPdk) 
    {
        //Get the SimObject Manager from the PDK Service
        hr = pPdk->QueryService(P3D::SID_SimObjectManager,  P3D::IID_ISimObjectManager, (void**)&pManager);
        
        if (SUCCEEDED(hr))
        {
            //Register your SimObject class with the SimObject Manager with its unique guid ID, friendly
            //category name, and address for its "create" function.
            hr = pManager->RegisterSimulationCategory(CLSID_MySimObject,     //Unique GUID
                                                      "Airplane",            //Friendly category
                                                      New);                  //Static "factory" function
        }
    }

    pManager->Release(); //Don't forget to release reference
    pPdk->Release();     //Don't forget to release reference
}

At this point, Prepar3D will load your DLL and register your SimObject implementation. It is important to note that this registration should occur in the call to your DLLStart(), which occurs early in the Prepar3D loading process. Shortly after that, validation occurs on all SimObjects against their specified simulation implementation.


SimObject Instance Lifecycle

The following sections will illustrate the lifecycle of a SimObject instance:creation, initialization, and destruction.

Note: The following code samples are meant to be general examples, and will not necessarily compile with the previous code without additional implementations, such as the IUnkonwn methods. The samples provided in this SDK will provide more thorough functioning code.

Creation

A SimObject instance is created when Prepar3D calls your factory function that was registered in the preceding steps. Prepar3D will call your function with an IBaseObject interface pointer. The base object is basically a proxy object that manages data between your SimObject implementation and other systems throughout Prepar3D. Your function must return a new instance of your ISimObject in the out-parameter.

Note:

Following is an example of a factory function. This would require a constructor to be added to your object class.

static HRESULT New(__in __notnull P3D::IBaseObject* pBaseObject, __out __notnull P3D::ISimObject** ppThisSim)
{
    *ppThisSim = new MySimObject(pBaseObject);

    return S_OK;
}

Loading Constant Data

Once your basic object is instantiated, Prepar3D will call your ISimObject implementation of LoadConstantData(). For performance reasons, Prepar3D will cache the data returned in the out-parameter and used for subsequent instances of the same SimObject. If the data exists for the new SimObject, Prepar3D will pass it in so that the same data can be used without having to reload it. It is important to note that this is only for data that can be shared across all instances of the same SimObject.

STDMETHODIMP MySimObject::LoadConstantData(__out void** ppConstantData) //on-disk data
{
    HRESULT hr = E_FAIL;

    //Do we already have a ref to loaded data due to a previous instance?
    if (*ppConstantData)
    {
        //Use this data
        m_pConstantData = reinterpret_cast<MySimObjectConstantData*>(*ppConstantData);
    }
    else //Else, Load the data, return a ref to it to be cached for subsequent instances
    {
        //Load data and return is
        m_pFixedData = new MySimObjectConstantData();

        hr = m_pConstantData->Load();

        *ppConstantData = const_cast<void*>(reinterpret_cast<const void*>(m_pConstantData));
    }

    return hr;
}

Loading Dynamic Data

In this loading phase, your SimObject may create subsystems that may be dependent on the constant data loaded in the previous phase. An example might be instantiating the number of engines on an airplane based on the data loaded in the previous phase.

STDMETHODIMP MySimObject::LoadDynamicData()
{
    HRESULT hr = E_FAIL;

    //Create your SimObject subsystems
    hr = S_OK;

    return hr;
}

Initialization

In this initialization phase, your SimObject should have all of its subsystems created. It might be used to initialize the state of a system or establish reference to other systems. An example might be references between an engine and a fuel system.

STDMETHODIMP MySimObject::Init()
{
    HRESULT hr = E_FAIL;

    //Initialize your subsystems
    hr = S_OK;

    return hr;

}

De-initialization

In this phase, your SimObject might need to release references between dependent subsystems.

STDMETHODIMP MySimObject::DeInit()
{
    HRESULT hr = E_FAIL;

    //De-initialize your subsystems
    hr = S_OK;

    return hr;
}

Unloading Constant Data

When the last instance of a SimObject is destroyed, Prepar3D will call UnLoadConstantData() to destroy it:

STDMETHODIMP MySimObject::UnLoadConstantData(__in void** ppConstantData) //on-disk data
{
    HRESULT hr = E_FAIL;

    if (*ppConstantData == m_pConstantData)
    {
        hr = m_ m_pConstantData->UnLoad();

        delete m_pConstantData;

        m_pConstantData = NULL;
        *ppConstantData = NULL;

        hr = S_OK;
    }

    return hr;
}

Destruction

Destruction will occur when the reference count on your SimObject reaches zero.


Creating Behaviors

Registering Simulation Callbacks

Real-time simulation callbacks are registered through the IBaseObject interface using the ISimulation interface. For example:

class MySimulation : public P3D::ISimulation
{
    STDMETHOD (Update)(double dDeltaT) override { /*Do simulation stuff*/ return S_OK;}
    STDMETHOD (SaveLoadState)(__in __notnull P3D::PSaveLoadCallback pfnCallback, __in BOOL bSave) override
    {
        if (bSave)
        {
            //Save states
        }
        else
        {
            //Load states
        }
    }
}


//SimObject definition
class MySimObject : public P3D::ISimObject
{
    ...
    MySimulation m_MySimulation;//Instance of MySimulation
}


//Function called to register simulation callbacks
void MySimObject::RegisterMySimulations()
{
    HRESULT hr = pBaseObject->RegisterSimulation(&m_MySimulation, 60.0f /*Hz*/);
}

Useful notes on implementing ISimulation classes:

Saving Simulation States

SaveLoadState() is called on all ISimulation implementations when Prepar3D executes both a Scenario Save and Scenario Load.

STDMETHODIMP MySimObject::SaveLoadState(__in __notnull P3D::PSaveLoadCallback pfnSaveLoadCallback, __in const BOOL bSave)
    {
    if (pfnSaveLoadCallback)
    {
                            /*Section Name*/   /*Instance*/  /*Keyword*/   /*Value*/
        pfnSaveLoadCallback("MySimObjectData",     0     , "MyDataValue_0", m_dVal0, bSave);
        pfnSaveLoadCallback("MySimObjectData",     0     , "MyDataValue_1", m_dVal1, bSave);
    }

    return S_OK;
}

Creating Properties and Events

Properties

This SDK enables a SimObject solution developer to create custom "properties" for their SimObject implementations. These are similar in nature to legacy Simulation Variables.

Example of static callbacks registered with RegisterProperty():

#include <atlcomcli.h>
#include <Pdk.h>
#include <ISimObject.h >

//The following 2 samples functions must be static

//Sample PPropertyCallback function
/*static*/ STDMETHODIMP MySimObject::GetMyDoubleProperty(__in const ISimObject& Sim, __out double& dProperty, __in int iIndex)
{
    dProperty = static_cast<const MySimObject&>(Sim).m_dMyDoublePropertyValue;

    return S_OK;
}

//Sample PPropertyVectorCallback function
/*static*/ STDMETHODIMP MySimObject::GetMyVectorProperty(__in const ISimObject& Sim, __out DXYZ& vProperty, __in int iIndex)
{
    vProperty = static_cast<const MySimObject&>(Sim).m_vMyVectorPropertyValue;

    return S_OK;
}

void__stdcall DLLStart(P3D::IPdk* pPdk)
{
    P3D::ISimObjectManager* pManager = NULL;

    HRESULT hr = E_FAIL;

    if (pPdk)
    {
        //Get the SimObject Manager from the PDK Service
        hr = pPdk->QueryService(P3D::SID_SimObjectManager, P3D::IID_ISimObjectManager, (void**)&pManager);
        if (SUCCEEDED(hr))
        {
            //Register your SimObject class with the SimObject Manager with its unique guid ID, friendly
            //category name, and address for its "create" function.
            hr = pManager->RegisterSimulationCategory(CLSID_MySimObject, //Unique GUID
                                                      "Airplane",        //Friendly category
                                                      New);              //Static "factory" function

            //Sample registration of properties
            pSimObjectMgr->RegisterProperty(CLSID_MySimObject, "MyDoubleProperty", "percent over 100", MySimObject::GetMyDoubleProperty);
            pSimObjectMgr->RegisterProperty(CLSID_MySimObject, "MyVectorProperty", "percent over 100", MySimObject::GetMyVectorProperty);

        }
    }

    pManager->Release();  //Don't forget to release reference
    pPdk->Release();      //Don't forget to release reference

}

Events

Similar to property registration, events are simply another type of property. They are analogous to Prepar3D's legacy Event IDs. The same guidelines listed above for properties also apply to events, with the following exceptions:

An example of event property registration looks similar to the properties registered above:

//The following 2 samples functions must be static

//Sample PEventCallback function
/*static*/STDMETHODIMP MySimObject:: MyKeyEventTrigger(__in ISimObject& Sim, __in double dProperty, __in int iIndex)
{
    static_cast<MySimObject>(Sim).m_bMyKeyState = !static_cast<MySimObject>(Sim).m_bMyKeyState; //Toggle

    return S_OK;
}

//Sample PEventCallback function
/*static*/ STDMETHODIMP MySimObject::MyJoyStickEvent(__in ISimObject& Sim, __in double dProperty, __in int iIndex)
{
    static_cast<const MySimObject>(Sim).m_dMyJoyStickEvent = dProperty;
    return S_OK;
}

void__stdcall DLLStart(P3D::IPdk* pPdk)
{
    ...
        //Sample registration of event property

        pSimObjectMgr->RegisterProperty(CLSID_MySimObject, "MyKeyEventProperty", "bool", MySimObject::MyKeyEventTrigger, EVENTTYPE_NORMAL);
        pSimObjectMgr->RegisterProperty(CLSID_MySimObject, "MyJoystickProperty", "percent over 100", MySimObject::MyJoyStickEvent) , EVENTTYPE_AXIS;
}