| Status | CategoryBlueprintApproved (do not edit if already approved in Launchpad) |
| Launchpad Entry | simulator-singleton |
| Modules affected | None |
| Responsible | MarcSchinnenburg |
Contents
Summary
Have one singleton that aggregates all other "Singletons" to improve testability.
Release Note
This change will cause all users to change the way they access the EventScheduler, RandomNumberGenerator and so on ... Thus it breaks the current API. However, it is easy to add backward compatibility.
Rationale
The following problem shall be solved by this redesign:
There is an indispensable need to access one global instance of a certain type of variable within a simulator. Well known examples are the EventScheduler and the RandomNumberGenerator. Others, like a data base to store measured values can be easily thought of. Typically the Singleton pattern is applied to this problem (see "Gamma, Design Patterns" for more information). This approach has considerable drawbacks related to testing. For each test a fresh and clean environment (consisting of wns::events::scheduler::Scheduler, wns::RandomNumberGenerator, ...) is needed. With the current approach (one Singleton for each of these variables) we need to reset each global variable (which is error prone, if we forget one). If we would have one Singleton (called wns::Simulator) we would be able to reset this Singleton (wns::Singelton::getInstance().reset()), effectively resetting all "Singletons" inside this Singleton (which is much less error prone ...).
Use Cases
In each simulation. In each test.
Requirements
- Global access to singletons (e.g. wns::events::scheduler::Scheduler) within the simulation
- Fresh singleton (e.g. wns::events::scheduler::Scheduler) for each test
Different versions of singletons (e.g. wns::events::scheduler::Scheduler: RealTime, FIFO, ...)
- Each of the singleton implementations need different arguments at construction time
Especially the last point is hard to solve with standard singletons, since these typically don't have parameters at all.
Design
The idea is to have only ONE singleton which aggregates all other Singletons in itself. This singleton needs to be constructed at the very beginning of the simulation (say after int main() {...), to ensure all other Singletons are available.
![]() |
| Figure: UML diagram of the Simulator interface and its associated singeltons |
The according interface in C++:
1 class ISimulator
2 {
3 public:
4 virtual events::scheduler::Scheduler*
5 getEventScheduler() = 0;
6
7 virtual RandomNumberGenerator*
8 getRandomNumberGenerator() = 0;
9
10 virtual DataBase*
11 getDataBase() = 0;
12
13 virtual logger::Master*
14 getMasterLogger() = 0;
15
16 virtual pyconfig::View
17 getConfiguration() = 0;
18 };
Additionally, we will have a SingletonHolder which provides the access to the Singleton:
1 class SimulatorSingletonHolder
2 {
3 public:
4 // No instance of ISimulator set at startup
5 SimulatorSingletonHolder();
6
7 // Throw if no instance avaiable
8 ISimulator*
9 getInstance();
10
11 // Inject instance, throw if instance already set
12 void
13 setInstance(ISimulator*);
14 };
15
16 static SimulatorSingeltonHolder GlobalSimulator;
Implementation
An implementation might look as follows:
1 // The normal Simulator, you would use in a simulation
2 class Simulator :
3 public ISimulator
4 {
5 public:
6 Simulator(const pyconfig::View& config) :
7 masterConfig(config)
8 {
9 this->setupEventScheduler(config.get('eventScheduler'));
10 this->setupRandomNumberGenerator(config.get('randomNumberGenerator'));
11 this->setupMasterLogger(config.get('masterLogger'));
12 this->setupDataBase(config.get('dataBase'));
13 }
14
15 pyconfig::View
16 getConfiguration()
17 {
18 return masterConfig;
19 }
20
21 // ... implementation of other getters here
22
23 private:
24 void
25 setupEventHandler(const pyconfig::View& config);
26
27 // ... and so on
28 };
A simulation core can then roughly look as follows:
![]() |
| Figure: UML Sequence Diagram of simulation startup |
or in C++:
1 int main(int argc, char* argv[])
2 {
3 // get config from somewhere ...
4
5 Simulator sim(config);
6 GlobalSimulator::setInstance(&sim);
7
8 // may be we need even more here:
9 sim.startup();
10 sim.run();
11 sim.shutdown();
12 }
Different implementation (with and without networking, GUI or other features) become easily possible now and can be cleanly separated.
UI Changes
1 EventScheduler* es = EventScheduler::getInstance();
2 // now reads
3 EventScheduler* es = wns::GlobalSimulator::getInstance()->getEventScheduler();
Code Changes
Code changes should include an overview of what needs to change, and in some cases even the specific details.
Migration
Include:
data migration, if any
redirects from old URLs to new ones, if any
how users will be pointed to the new way of doing things, if necessary.
Test/Demo Plan
It's important that we are able to test new features, and demonstrate them to users. Use this section to describe a short plan that anybody can follow that demonstrates the feature is working. This can then be used during CD testing, and to show off after release.
This need not be added or completed until the specification is nearing beta.
Open Issues
This should highlight any issues that should be addressed in further specifications, and not problems with the specification itself; since any specification with problems cannot be approved.
BoF agenda and discussion
Use this section to take notes during the BoF; if you keep it in the approved spec, use it for summarising what was discussed and note any options that were rejected.

