C++ Basic Framework Design for a Simulation System

C++ Basic Framework Design for a Simulation System admin 15 February, 2011 - 11:34

This set of code is derived from the work performed building the Direct2Learning Java Avionics training platform (now defunct).

It uses multiple inheritance, however base classes are pure virtual effectively to provide interfaces.

The code sample is missing the complicated ExecScheduler which is where the scheduling of modules is performed. Writing the ExecScheduler is reasonably complex - it needs to work with threads and provide timing. I've got a Java version of this which is at the end for illustration.

This code is a prototype model for proof of concept - it compiles but won't run because there are too many required subsytems missing.

#include "stdafx.h"
using namespace std;

//
// Fundamental element of simulation - an ExecModule is a concise unit of simulation work. It will
// be called by the main real time executive at the frequency specified by the getExecRate() method.
// Communication between modules is either via datapool, or by using BusMessages.
class ExecModule
{
public:
	virtual bool initialise(long time_ms) = 0;
    virtual long run(long ms)  = 0;
    virtual long getExecRate()  = 0;
    virtual string getModuleDescription() = 0;
}

class GeoCoordinate
{
public:
	GeoCoordinate(double lat, double lon, double alt);
};

class Model
{
public:
	virtual void DifferentialEquations() = 0;
	virtual void AlgebraicEquations() = 0;

};
class State
{
public:
	Value Prime();
	Prime(Value &v);
    State operator *(State c);
}

class AircraftModel : public ExecModule, public Model
{
private:
	State x1, x2;
    State x3;
    InputDouble u;
    InputBool flag1, flag2;
    AlgebraicDouble  x1x2;
    Model tw1, tw2; // engine
    Model gear;
    Model isa;
    TrimRoutine HorizontalFight;
    TrimRoutine OnGround, General;
    ConstantDouble c1, c2;
    ConstantInt : ci1;

public:
	AircraftModel()
    {
    }

public:
	virtual void DifferentialEquations()
	{
		x1.Prime(2.0*x2);
		x2.Prime(x1x2);
	}

	virtual void AlgebraicEquations()
	{
		x1x2 = x1 * x2 + x1.Prime();
	}

public: // Required for ExecModule
	string getModuleDescription()
    {
        return "Aircraft Model";
    }

    long getExecRate()
    {
        return 33L;//ms (30hz)
    }

    long run(long ms)
    {
        return 0L;
    }

	bool initialise(long time_ms)
    {
		// called by the Exec (in sequence) when initialisation is required.
    }

};

class SimLoad
{
public:
// exec modules to load
    class Model *aircraft_model;
    class Model *engine_model;
	class Model *aerodynamics_model;
    class GPSSimulator *gps;
    class FeaturesDataProvider *runways;
    class ArincDB *arincDB;
	class ExecSystem *execSystem;

	SimLoad()
    {
		engine_model = new EngineModel();
		aerodynamics_model = new AeroDynamicsModel();
        aircraft_model = new AircraftModel();
        arincDB = new ArincDB();
        gps = new GPSSimulator();

        // ensure that the simulated systems are loaded in the correct
        // sequence. Notice that the exec system provides two schedulers which
		// we select manually to allow for threading. Each thread within the exec is 
		// synchronised at the start of each frame (iteration) however within each frame
		// each thread is free running so care needs to be taken when scheduling dependant
		// modules across different threads.
        execSystem.scheduler.addModule(engine_model);
		execSystem.scheduler.addModule(aerodynamics_model);
		execSystem.scheduler.addModule(aircraft_model);
        execSystem.scheduler1.addModule(gps);

        runways = new ArincRunwayProvider(arincDB);

		execSystem.start(); // 
    }
}


int _tmain(int argc, _TCHAR* argv[])
{
	return 0;
}

/**
 * Title: System Executive Module Scheduler
 *
 * Description: Schedules the execution at the rates specified by the modules {@link ExecModule}. Monitors
 * for module and system overruns and notes these.
 *
 * There could be many exec schedulers running; however any individual ExecModule must only be invoked by one
 * scheduler. This is a very simplistic approach to scheduling but avoids the problems with thread safety, and is
 * therefore more efficient, less susceptible to race conditions, etc.
 *
 * Using a thread-based approach is akin to anarchy by comparison as there is little overall control over the
 * sequencing and timing. With the simulated systems that we have the sequencing is important, and also the concept
 * of a frame based iteration such that the modules are all executed once per frame in the right sequence.
 *
 * The disadvantage of this method is not automatically taking advantage of extra (multiple) CPUs; however this
 * is outweighed by the advantage of being able to control the schedule of operating process.
 *
 */
public class ExecScheduler
{
    Timer 		timer;
    ExecTimer 	ET;
    Vector 		ModuleList;		// list of modules to execute

/**
 * rate at which this thread is iterated which is equivalent to the maximum frame rate of the system.
 * (reliant on the java thread callback accuracy)
 */
    private long ms_rate = 1000 / 20; // hz
    timerExec timer_exec;
    static Date current_date = new Date();

    private class sModule
    {
        private ExecModule	EM;
        long 				last_exec_time;
/**
 * Intialise, set up exec timer and make note of related execmodule
 *
 * @param _m ExecModule
 */
        sModule(ExecModule _EM)
        {
            last_exec_time = ET.getElapsedTime_mS();
            EM = _EM;
        }
        ExecTimer et = new ExecTimer();
        long peak_time = 0, avg_time = 0, overruns=0;

/**
 * Entry point for exec. handle scheduling, monitor run time.
 *
 * @return long
 */
        long run()
        {
            // use actual elapsed time, as we cannot guarantee hard timing
            // and accurate scheduling.
            long timeval = et.getElapsedTime_mS() - last_exec_time;
            if(timeval > EM.getExecRate())
            {
                last_exec_time = et.getElapsedTime_mS();
                long rv = EM.run(timeval);
                long exec_time = et.getElapsedTime_mS() - last_exec_time;
                peak_time = Math.max(exec_time, peak_time);
                if(avg_time == 0)
                    avg_time = exec_time;
                avg_time = (avg_time + peak_time) / 2;
            }

            return 0;
        }

/**
 * Initiate the exec module intialisation
 *
 * @param time long
 * @return long
 */
        long init(long time)
        {
            ExecTimer et = new ExecTimer();
            EM.initialise(0);
            System.out.println("ExecInit: " + EM.getModuleTitle() + "@" + 1000.0 / EM.getExecRate() + "hz, took " + et.getElapsedTime_mS() / 1000.0);

            return 0;
        }

/**
 * Count overruns for this module. A module is considered to have overrun if it takes more than its frequency
 * timeslice and therefore slews the next exec.
 */
        public void notify_overrun()
        {
            overruns++;
        }

/**
 * dumpStats
 */
        public void dumpStats()
        {
            System.out.println("Exec: "+EM.getModuleTitle()+ " peak " + peak_time
                               + " avg_time " + avg_time + " Overruns "+overruns+" Rate " + 1000/EM.getExecRate() + "hz");
        }

        public ExecModule getModule()
        {
            return EM;
        }

        public long get_avg_time()
        {
            return avg_time;
        }
    }

    public ExecScheduler()
    {
        ModuleList = new Vector(100);
        timer = new Timer();
        ET = new ExecTimer();
    }

    class timerExec extends TimerTask
    {
        private long ms_rate;

/**
 * timerExec
 *
 * @param _ms_rate long
 */
        public timerExec(long _ms_rate)
        {
            ms_rate = _ms_rate;
        }

/**
 * run - iterate through the list of modules and execute then according to the rate specifiers.
 */
        long fc = 0; // frame counter
        long spare_ft_percent = -1;
        long overrun; //overrun counter.
        public long getSpareTimePercent()
        {
            return spare_ft_percent;
        }

/**
 * Heart of the scheduler. Called by the Java runtime thread at the specified frequency. Iterate through the loaded
 * modules and run if necessary (handled by sModule). Collect stats for monitor.
 */
        public void run()
        {
            Enumeration E = ModuleList.elements();
            long ms_since_last_frame = ET.getElapsedTime_mS();
            long variation = ms_rate - ms_since_last_frame;

// Set the current date here for efficiency and possible record replay/snapshot type of operations.

            current_date = new Date();

            if (ms_since_last_frame > ms_rate && Math.abs(variation) > 20)
            {
                overrun++;
//               System.out.println("Overrun #" + overrun + " v" + variation + " ms" +ms_since_last_frame+"["+ms_rate);
            }
            ET.resetTimer();

            while(E.hasMoreElements())
            {
                sModule sm = (sModule)E.nextElement();
                sm.run();
                if (ET.getElapsedTime_mS() > ms_rate)
                    sm.notify_overrun();
            }
            // now calculate the frame timing, %spare and average spare frame time
            spare_ft_percent = 100 - ( (ET.getElapsedTime_mS() * 100 / ms_rate));

            if(ET.getElapsedTime_mS() > ms_rate)
                spare_ft_percent = 0;
        }
    }

    /**
     * dumpStats
     */
    public void dumpStats()
    {
        Enumeration E = ModuleList.elements();
        System.out.println("Exec Scheduler stats");
        long avg_time = 0;
        while(E.hasMoreElements())
        {
            sModule sm = (sModule)E.nextElement();
            sm.dumpStats();
            avg_time += sm.get_avg_time();
        }
        long spare_ft_percent = 100 - ( (avg_time * 100 / ms_rate));
        if(timer_exec != null)
            System.out.println("Exec: Avg free time "+ timer_exec.getSpareTimePercent() + "% " + avg_time+"%");
    }

/**
 * start - init rate, and init exec modules
 *
 * @param _ms_rate is the length in milliseconds of a frame.
 */
    public void start(long _ms_rate)
    {
        if (_ms_rate != 0)
            ms_rate = _ms_rate;

        Enumeration E = ModuleList.elements();
        System.out.println("Exec Scheduler start");
        while(E.hasMoreElements())
        {
            sModule sm = (sModule)E.nextElement();
            sm.init(0);
        }

        timer_exec = new timerExec(ms_rate);
        timer.scheduleAtFixedRate(timer_exec, 0, ms_rate);
    }

/**
 * start - no rate, so call other method with zero which will init
 * modules but not init the ms_rate
 */
    public void start()
    {
        start(0);
    }

    public void stop()
    {
        if (timer_exec != null)
            timer_exec.cancel();

        timer_exec = null;
    }


/**
 * Adds module that isn't already present to the execution list
 *
 * @param EM ExecModule
 * @return boolean
 */
    public boolean addModule(ExecModule EM)
    {
        Enumeration E = ModuleList.elements();
        while(E.hasMoreElements())
        {
            sModule sm = (sModule)E.nextElement();
            // pass value of elapsed time since the last frame - not the elapsed time
            // since this last invokation.
            if(sm.getModule() == EM)
                return false;
        }

        ModuleList.add(new sModule(EM));

        return true;
    }

/**
 * Removes a module from the execution list
 *
 * @param EM ExecModule
 * @return boolean
 */
    public synchronized boolean removeModule(ExecModule EM)
    {
        ModuleList.remove(EM);
        return true;
    }

    /**
     * Removes a module from the execution list
     *
     * @param EM ExecModule
     * @return boolean
     */
        public synchronized boolean removeAllModules()
        {
            ModuleList.clear();
            return true;
        }

/**
 * @return Date in the simulated environment. This is usally the same
 * as the system date; however it could be different, eg. record replay.
 */
    public static Date getCurrentDate()
    {
        return current_date;
    }
}