Newer
Older
Martin Christoph Hierholzer
committed
/*
* Application.h
*
* Created on: Jun 07, 2016
* Author: Martin Hierholzer
*/
#ifndef CHIMERATK_APPLICATION_H
#define CHIMERATK_APPLICATION_H
#include <mutex>
Martin Christoph Hierholzer
committed
#include <mtca4u/DeviceBackend.h>
Martin Christoph Hierholzer
committed
#include <ChimeraTK/ControlSystemAdapter/ApplicationBase.h>
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
#include "ApplicationException.h"
#include "VariableNetwork.h"
#include "Flags.h"
#include "InternalModule.h"
Martin Christoph Hierholzer
committed
#include "EntityOwner.h"
#include "TriggerFanOut.h"
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
namespace ChimeraTK {
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
class Module;
Martin Christoph Hierholzer
committed
class AccessorBase;
Martin Christoph Hierholzer
committed
class VariableNetwork;
Martin Christoph Hierholzer
committed
template<typename UserType>
class Accessor;
Martin Christoph Hierholzer
committed
template<typename UserType>
class TestDecoratorRegisterAccessor;
Martin Christoph Hierholzer
committed
class Application : public ApplicationBase, public EntityOwner {
Martin Christoph Hierholzer
committed
public:
Martin Christoph Hierholzer
committed
Application(const std::string& name)
Martin Christoph Hierholzer
committed
: ApplicationBase(name), EntityOwner(nullptr, name) {}
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
~Application() {}
Martin Christoph Hierholzer
committed
/** This will remove the global pointer to the instance and allows creating another instance
Martin Christoph Hierholzer
committed
* afterwards. This is mostly useful for writing tests, as it allows to run several applications sequentially
* in the same executable. Note that any ApplicationModules etc. owned by this Application are no longer
* valid after destroying the Application and must be destroyed as well (or at least no longer used). */
Martin Christoph Hierholzer
committed
void shutdown();
Martin Christoph Hierholzer
committed
/** Define the connections between process variables. Must be implemented by the application developer. */
virtual void defineConnections() = 0;
void initialise();
Martin Christoph Hierholzer
committed
void run();
Martin Christoph Hierholzer
committed
/** Check if all connections are valid. */
void checkConnections();
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
/** Instead of running the application, just initialise it and output the published variables to an XML file. */
void generateXML();
Martin Christoph Hierholzer
committed
/** Output the connections requested in the initialise() function to std::cout. This may be done also before
* makeConnections() has been called. */
void dumpConnections();
Martin Christoph Hierholzer
committed
/** Obtain instance of the application. Will throw an exception if called before the instance has been
* created by the control system adapter, or if the instance is not based on the Application class. */
static Application& getInstance();
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
/** Enable the testable mode. This allows to pause and resume the application for testing purposes using the
* functions pauseApplication() and resumeApplication(). The application will start in paused state.
*
* This function must be called before the application is initialised (i.e. before the call to initialise()).
*
* Note: Enabling the testable mode will have a singificant impact on the performance, since it will prevent
* any module threads to run at the same time! */
Martin Christoph Hierholzer
committed
void enableTestableMode() { testableMode = true; pauseApplication(); }
Martin Christoph Hierholzer
committed
/** Lock the mutex for the testable mode, which prevents any application thread from running. This works only if
* enableTestableMode() was called before. */
Martin Christoph Hierholzer
committed
void pauseApplication() { assert(testableMode); getTestableModeLockObject().lock(); }
Martin Christoph Hierholzer
committed
/** Unlock the mutex for the testable mode, which allows the application threads to run again. This works only if
* enableTestableMode() was called before. */
Martin Christoph Hierholzer
committed
void resumeApplication() { assert(testableMode); getTestableModeLockObject().unlock(); }
Martin Christoph Hierholzer
committed
/** Resume the application until all application threads are stuck in a blocking read operation. Must be called
* while the application is paused. */
void stepApplication() {
while(true) {
testableMode_counter = 0;
resumeApplication();
usleep(100);
pauseApplication();
if(testableMode_counter == 0) break;
}
}
Martin Christoph Hierholzer
committed
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
/** Obtain the lock object for the testable mode lock for the current thread. The returned object has
* thread_local storage duration and must only be used inside the current thread. Initially (i.e. after
* the first call in one particular thread) the lock will not be owned by the returned object, so it is
* important to catch the corresponding exception when calling std::unique_lock::unlock(). */
static std::unique_lock<std::mutex>& getTestableModeLockObject() {
thread_local static std::unique_lock<std::mutex> myLock(Application::getInstance().testableMode_mutex,
std::defer_lock);
return myLock;
}
/** This is a testable version of mtca4u::TransferElement::readAny(). Always use this version instead of the
* original version provided by DeviceAccess. If the testable mode is not enabled, just the original version
* is called instead. Only with the testable mode enabled, special precautions are taken to make this blocking
* call testable. */
boost::shared_ptr<TransferElement> readAny(std::list<std::reference_wrapper<TransferElement>> elementsToRead) {
if(!testableMode) {
return mtca4u::TransferElement::readAny(elementsToRead);
}
else {
auto &theLock = getTestableModeLockObject();
try {
theLock.unlock();
}
catch(std::system_error &e) { // ignore operation not permitted errors, since they happen the first time (lock not yet owned)
if(e.code() != std::errc::operation_not_permitted) throw e;
}
auto ret = mtca4u::TransferElement::readAny(elementsToRead);
assert(theLock.owns_lock()); // lock is acquired inside readAny(), since TestDecoratorTransferFuture::wait() is called there.
return ret;
}
}
Martin Christoph Hierholzer
committed
protected:
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
friend class Module;
Martin Christoph Hierholzer
committed
friend class VariableNetwork;
Martin Christoph Hierholzer
committed
friend class VariableNetworkNode;
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
template<typename UserType>
friend class Accessor;
Martin Christoph Hierholzer
committed
/** Register the connections to constants for previously unconnected nodes. */
void processUnconnectedNodes();
Martin Christoph Hierholzer
committed
/** Make the connections between accessors as requested in the initialise() function. */
void makeConnections();
Martin Christoph Hierholzer
committed
/** Make the connections for a single network */
void makeConnectionsForNetwork(VariableNetwork &network);
/** UserType-dependent part of makeConnectionsForNetwork() */
Martin Christoph Hierholzer
committed
template<typename UserType>
void typedMakeConnection(VariableNetwork &network);
Martin Christoph Hierholzer
committed
/** Register a connection between two VariableNetworkNode */
Martin Christoph Hierholzer
committed
VariableNetwork& connect(VariableNetworkNode a, VariableNetworkNode b);
Martin Christoph Hierholzer
committed
/** Perform the actual connection of an accessor to a device register */
Martin Christoph Hierholzer
committed
template<typename UserType>
boost::shared_ptr<mtca4u::NDRegisterAccessor<UserType>> createDeviceVariable(const std::string &deviceAlias,
Martin Christoph Hierholzer
committed
const std::string ®isterName, VariableDirection direction, UpdateMode mode, size_t nElements);
Martin Christoph Hierholzer
committed
/** Create a process variable with the PVManager, which is exported to the control system adapter. nElements will
be the array size of the created variable. */
Martin Christoph Hierholzer
committed
template<typename UserType>
Martin Christoph Hierholzer
committed
boost::shared_ptr<mtca4u::NDRegisterAccessor<UserType>> createProcessVariable(VariableNetworkNode const &node);
Martin Christoph Hierholzer
committed
/** Create a local process variable which is not exported. The first element in the returned pair will be the
* sender, the second the receiver. nElements will be the array size of the created variable. */
Martin Christoph Hierholzer
committed
template<typename UserType>
std::pair< boost::shared_ptr<mtca4u::NDRegisterAccessor<UserType>>, boost::shared_ptr<mtca4u::NDRegisterAccessor<UserType>> >
createApplicationVariable(size_t nElements, const std::string &name);
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
/** Register an application module with the application. Will be called automatically by all modules in their
* constructors. */
Martin Christoph Hierholzer
committed
void overallRegisterModule(Module &module) {
overallModuleList.push_back(&module);
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
/** List of application modules */
Martin Christoph Hierholzer
committed
std::list<Module*> overallModuleList; /// @todo TODO FIXME maybe recursing through all modules is better than having an additional overall list?
Martin Christoph Hierholzer
committed
/** List of InternalModules */
std::list<boost::shared_ptr<InternalModule>> internalModuleList;
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
/** List of variable networks */
std::list<VariableNetwork> networkList;
Martin Christoph Hierholzer
committed
/** List of constant variable nodes */
std::list<VariableNetworkNode> constantList;
Martin Christoph Hierholzer
committed
/** Map of trigger sources to their corresponding TriggerFanOuts. Note: the key is the unique ID of the
* triggering node. */
std::map<const void*, boost::shared_ptr<TriggerFanOut>> triggerMap;
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
/** Create a new, empty network */
VariableNetwork& createNetwork();
Martin Christoph Hierholzer
committed
/** Instance of VariableNetwork to indicate an invalid network */
VariableNetwork invalidNetwork;
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
/** Map of DeviceBackends used by this application. The map key is the alias name from the DMAP file */
std::map<std::string, boost::shared_ptr<mtca4u::DeviceBackend>> deviceMap;
Martin Christoph Hierholzer
committed
/** Flag if connections should be made in testable mode (i.e. the TestDecoratorRegisterAccessor is put around all
* push-type input accessors etc.). */
bool testableMode{false};
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
/** Mutex used in testable mode to take control over the application threads. Use only through the lock object
* obtained through getLockObjectForCurrentThread() */
Martin Christoph Hierholzer
committed
std::mutex testableMode_mutex;
/** Counter used in testable mode to check if application code was executed after releasing the testableMode_mutex.
* This value may only be accessed while holding the testableMode_mutex. */
size_t testableMode_counter{0};
template<typename UserType>
friend class TestDecoratorRegisterAccessor; // needs access to the testableMode_mutex and testableMode_counter
template<typename UserType>
friend class TestDecoratorTransferFuture; // needs access to the testableMode_mutex and testableMode_counter
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
};
} /* namespace ChimeraTK */
#endif /* CHIMERATK_APPLICATION_H */