-
Notifications
You must be signed in to change notification settings - Fork 1
Using the new simulation config system
This guide focuses on the use of the new system to select between different implementations of an interface at runtime.
In order to simplify the process of runtime configuration, as well as simplify incorporation of simulated(fake) code, the config system has been designed to parse the text files in the configurations directory. This allows for certain settings and features to be set and enabled at runtime and for larger portions of the code to be independent of the particular implementation of an object/driver.
In particular this is allows for easy switching between simulated code and the real implementation, replacing the commandline argument method used in previous years.
- Register the implementation with the InterfaceFactory and provide a helper function to create object.
- Add new interface to
interfaces.hpp
. - Create the object by requesting an instance from
sys.config
. - Use the object as normal.
Important: This guide is written to be used in conjunction with the comments provided in all files mentioned, so when a file is mentioned check it for comments relating to the usage of that file.
In order to use the new interface infrastructure optimally the following should first be done.
- Firstly, classes to be simulated should inherit from a common interface.
- All functions that will be called must be present in the interface and implemented in all implementations.
- Any code specific to one implementation should be refactored so that it is independent of which implementation it receives. Anything specific to each implementation should be handled in the functions called.
In order to add a new interface all that needs to be done is:
- Add the interface to the list contained within interfaces.hpp in the utils directory. See the comments there, or the example below for guidance.
That is all that is required to add the interface. Now implementations can be provided.
Adding a new implementation for an interface that has already been added to interfaces.hpp can be done as follows:
- Ensure that the implementation implements all the required functionality from the interface.
- Provide a helper function that calls the implementation's constructor. (see the example below)
- Add the interface and class to the config.txt file, under the InterfaceFactory heading. The format is
InterfaceName ImplementationName
. The implementation name is the name of the class that will be used to create the instance.
Once that is done we are now ready to get and use an instance of the implementation chosen.
Instead of checking a flag to determine which instance to create (eg. real vs fake), call the dedicated function from within sys.config
that will construct and provide the requested object based on the parsed configurations text file. This function will be generated for you provided the interface was added correctly to interfaces.hpp, and will be of the form sys.config->interfaceFactory.get<InterfaceName>Instance
.
Note: It is important to realise that this means that the code calling any member functions of the instance does not know which implementation of the interface it is receiving and as such must be written generally so that it does not care which implementation it is receiving.
The created object can then just be used as normal without worrying about which implementation of the interface has actually been created.
Note: Due to the way the system is structured, the constructor cannot take arguments. One way around this is to use an init function to initialise the created object with the necessary arguments.
This example can be found in the hyped codebase at the moment with the implementation files and interface in src/demo and the demo executable (factory.cpp) in the run directory.
If I had two classes called Implementation1
and Implementation2
which both implement the same functionality in different ways, both inheriting from a class called DemoInterface
, and I wanted to select between them at runtime I could use the interface infrastructure to achieve this.
First, the interface must be added to utils/interfaces.hpp. This file contains comments that explain how exactly to add the new interface. The part to edit looks something like this:
#define INTERFACE_LIST(REGISTER) \
REGISTER(sensors, ImuInterface)
The format used is REGISTER('namespace', 'InterfaceName')
. Where the namespace is the namespace of your interface with the leading hyped::
dropped. So after adding the demo, which is in namespace 'demo', the code would look like this:
#define INTERFACE_LIST(REGISTER) \
REGISTER(sensors, ImuInterface) \
REGISTER(demo, DemoInterface)
The the rest of the overhead is taken care of by the macros, and so now the interface can be used.
Next, within the .cpp file for each implementation provide a helper function that calls the implementation's constructor. It must then be registered with the interface factory passing the name and helper function.
This is demonstrated in the code for Implementation1:
namespace {
// this is how the config system will create the instance of Implementation1
DemoInterface* createImplementation1()
{
return new Implementation1();
}
// register the implementation with the factory
bool reg_impl = utils::InterfaceFactory<DemoInterface>
::registerCreator("Implementation1", createImplementation1);
}
Implementation1 and DemoInterface can just be replaced with the specific implementation and interface this is being implemented for.
Finally, in order to use the interface we must obtain an instance. The config code handles this as it determines what we actually receive, so the object would be "created" like this:
demo::DemoInterface* demo = sys.config->interfaceFactory.getDemoInterfaceInstance();
What this function returns is determined by the config.txt file which is parsed at runtime. There is a section in the config.txt file with the heading InterfaceFactory
this is where the implementations can be selected. The format is InterfaceName ImplementationName
. So selecting Implementation1 for the DemoInterface would look like this:
> InterfaceFactory
DemoInterface Implementation1
Changing to Implementation2 just requires changing Implementation1
to Implementation2
in the above.
- Home
- How to add and edit pages on the wiki
- Glossary
- Admin
- Projects & Subsystems
- Motor Controllers
- Navigation
- Quality Assurance
- Sensors
- State Machine
- Telemetry
- Technical Guides
- BeagleBone Black (BBB)
- Configuration
- Contributing
- Testing
- Install VM on Mac
- Makefiles
- Reinstall MacOS Mojave
- Travis Troubleshooting
- Knowledge Base