Commit e391959b authored by Georg Martin Reinke's avatar Georg Martin Reinke
Browse files

encapsulate simulation state / communication in a Python class


Former-commit-id: 2c7053e9
parent c9532b89
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#include <thread> #include <thread>
#include "CIMReader.h" #include "CIMReader.h"
#include "PySimulation.h"
#include "Simulation.h" #include "Simulation.h"
#include "ShmemInterface.h" #include "ShmemInterface.h"
...@@ -38,153 +39,27 @@ bool parseInt(const char *s, int *i) { ...@@ -38,153 +39,27 @@ bool parseInt(const char *s, int *i) {
return (end != s && !*end); return (end != s && !*end);
} }
std::vector<BaseComponent*> components; static PyModuleDef dpsimModule = {
PyModuleDef_HEAD_INIT, "dpsim", NULL, -1, NULL,
enum SimState { NULL, NULL, NULL, NULL
StateStopped = 0,
StateRunning,
StatePaused,
StateDone
};
static std::thread *simThread;
struct SimContext {
Simulation &sim;
Logger &log, &llog, &rlog;
std::condition_variable cond;
std::mutex mut;
std::atomic_int stop, numStep;
SimState state;
SimContext(Simulation&, Logger&, Logger&, Logger&);
}; };
SimContext::SimContext(Simulation &sim, Logger &log, Logger &llog, Logger &rlog) : static PyObject* PyInit_dpsim(void) {
sim(sim), log(log), llog(llog), rlog(rlog), mut(), cond() { PyObject* m;
this->stop = 0;
this->numStep = 0;
this->state = StateStopped;
}
static SimContext *globalCtx;
static void simThreadFunction(SimContext* ctx) {
bool running = true;
std::unique_lock<std::mutex> lk(ctx->mut, std::defer_lock);
ctx->numStep = 0;
while (running) {
running = ctx->sim.step(ctx->log, ctx->llog, ctx->rlog);
ctx->numStep++;
ctx->sim.increaseByTimeStep();
if (ctx->stop) {
lk.lock();
ctx->state = StatePaused;
ctx->cond.notify_one();
ctx->cond.wait(lk);
ctx->state = StateRunning;
lk.unlock();
}
}
lk.lock();
ctx->state = StateDone;
ctx->cond.notify_one();
}
static PyObject* pythonStart(PyObject *self, PyObject *args) {
std::unique_lock<std::mutex> lk(globalCtx->mut);
if (globalCtx->state == StateRunning) {
PyErr_SetString(PyExc_SystemError, "Simulation already started");
return nullptr;
} else if (globalCtx->state == StateDone) {
PyErr_SetString(PyExc_SystemError, "Simulation already finished");
return nullptr;
} else if (globalCtx->state == StatePaused) {
globalCtx->stop = 0;
globalCtx->cond.notify_one();
} else {
globalCtx->stop = 0;
globalCtx->state = StateRunning;
simThread = new std::thread(simThreadFunction, globalCtx);
}
Py_INCREF(Py_None);
return Py_None;
}
static PyObject* pythonStep(PyObject *self, PyObject *args) { if (PyType_Ready(&PySimulationType) < 0)
std::unique_lock<std::mutex> lk(globalCtx->mut);
int oldStep = globalCtx->numStep;
if (globalCtx->state == StateStopped) {
globalCtx->state = StateRunning;
globalCtx->stop = 1;
simThread = new std::thread(simThreadFunction, globalCtx);
} else if (globalCtx->state == StatePaused) {
globalCtx->stop = 1;
globalCtx->cond.notify_one();
} else if (globalCtx->state == StateDone) {
PyErr_SetString(PyExc_SystemError, "Simulation already finished");
return nullptr;
} else {
PyErr_SetString(PyExc_SystemError, "Simulation currently running");
return nullptr; return nullptr;
}
while (globalCtx->numStep == oldStep)
globalCtx->cond.wait(lk);
Py_INCREF(Py_None);
return Py_None;
}
static PyObject* pythonPause(PyObject *self, PyObject *args) { m = PyModule_Create(&dpsimModule);
std::unique_lock<std::mutex> lk(globalCtx->mut); if (!m)
if (globalCtx->state != StateRunning) {
PyErr_SetString(PyExc_SystemError, "Simulation not currently running");
return nullptr; return nullptr;
}
globalCtx->stop = 1;
globalCtx->cond.notify_one();
while (globalCtx->state == StateRunning)
globalCtx->cond.wait(lk);
Py_INCREF(Py_None);
return Py_None;
}
static PyObject* pythonWait(PyObject *self, PyObject *args) { Py_INCREF(&PySimulationType);
std::unique_lock<std::mutex> lk(globalCtx->mut); PyModule_AddObject(m, "Simulation", (PyObject*) &PySimulationType);
if (globalCtx->state == StateDone) { return m;
Py_INCREF(Py_None);
return Py_None;
} else if (globalCtx->state == StateStopped) {
PyErr_SetString(PyExc_SystemError, "Simulation not currently running");
return nullptr;
} else if (globalCtx->state == StatePaused) {
PyErr_SetString(PyExc_SystemError, "Simulation currently paused");
return nullptr;
}
while (globalCtx->state == StateRunning)
globalCtx->cond.wait(lk);
Py_INCREF(Py_None);
return Py_None;
}
static PyMethodDef pythonMethods[] = {
{"start", pythonStart, METH_VARARGS, "Start the simulation, or resume if it is paused."},
{"step", pythonStep, METH_VARARGS, "Perform a single simulation step."},
{"pause", pythonPause, METH_VARARGS, "Pause the already running simulation."},
{"wait", pythonWait, METH_VARARGS, "Wait for the simulation to finish."},
{NULL, NULL, 0, NULL}
};
static PyModuleDef dpsimModule = {
PyModuleDef_HEAD_INIT, "dpsim", NULL, -1, pythonMethods,
NULL, NULL, NULL, NULL
};
static PyObject* PyInit_dpsim(void) {
return PyModule_Create(&dpsimModule);
} }
int doPythonLoop(SimContext* sim) { void doPythonLoop() {
PyImport_AppendInittab("dpsim", &PyInit_dpsim); PyImport_AppendInittab("dpsim", &PyInit_dpsim);
Py_Initialize(); Py_Initialize();
...@@ -347,8 +222,7 @@ int cimMain(int argc, const char* argv[]) { ...@@ -347,8 +222,7 @@ int cimMain(int argc, const char* argv[]) {
Simulation sim(components, frequency, timestep, duration, log); Simulation sim(components, frequency, timestep, duration, log);
if (python) { if (python) {
globalCtx = new SimContext(sim, log, llog, rlog); doPythonLoop();
doPythonLoop(globalCtx);
} else { } else {
#ifdef __linux__ #ifdef __linux__
if (intf) if (intf)
......
#include "PySimulation.h"
using namespace DPsim;
void PySimulation::simThreadFunction(PySimulation* pySim) {
bool notDone = true;
std::unique_lock<std::mutex> lk(*pySim->mut, std::defer_lock);
pySim->numStep = 0;
while (pySim->running && notDone) {
notDone = pySim->sim->step(*pySim->log);
pySim->numStep++;
pySim->sim->increaseByTimeStep();
if (pySim->sigPause) {
lk.lock();
pySim->state = StatePaused;
pySim->cond->notify_one();
pySim->cond->wait(lk);
pySim->state = StateRunning;
lk.unlock();
}
}
lk.lock();
pySim->state = StateDone;
pySim->cond->notify_one();
}
PyObject* PySimulation::newfunc(PyTypeObject* type, PyObject *args, PyObject *kwds) {
PySimulation *self;
self = (PySimulation*) type->tp_alloc(type, 0);
if (self) {
// since mutex, thread etc. have no copy-constructor, but we can't use
// our own C++ constructor that could be called from python, we need to
// implement them as pointers
self->cond = new std::condition_variable();
self->mut = new std::mutex();
}
return (PyObject*) self;
}
int PySimulation::init(PySimulation* self, PyObject *args, PyObject *kwds) {
// TODO: actually parse arguments (frequency, timestep etc.)
self->log = new Logger("log.txt");
self->sim = new Simulation(components, 2*PI*50, 1e-3, 0.3, *self->log);
return 0;
};
void PySimulation::dealloc(PySimulation* self) {
if (self->simThread) {
// We have to cancel the running thread here, because otherwise self can't
// be freed.
PySimulation::stop((PyObject*)self, NULL);
delete self->simThread;
}
if (self->sim)
delete self->sim;
delete self->mut;
delete self->cond;
Py_TYPE(self)->tp_free((PyObject*)self);
}
PyObject* PySimulation::start(PyObject *self, PyObject *args) {
PySimulation *pySim = (PySimulation*) self;
std::unique_lock<std::mutex> lk(*pySim->mut);
if (pySim->state == StateRunning) {
PyErr_SetString(PyExc_SystemError, "Simulation already started");
return nullptr;
} else if (pySim->state == StateDone) {
PyErr_SetString(PyExc_SystemError, "Simulation already finished");
return nullptr;
} else if (pySim->state == StatePaused) {
pySim->sigPause = 0;
pySim->cond->notify_one();
} else {
pySim->sigPause = 0;
pySim->state = StateRunning;
pySim->running = true;
pySim->simThread = new std::thread(simThreadFunction, pySim);
}
Py_INCREF(Py_None);
return Py_None;
}
PyObject* PySimulation::step(PyObject *self, PyObject *args) {
PySimulation *pySim = (PySimulation*) self;
std::unique_lock<std::mutex> lk(*pySim->mut);
int oldStep = pySim->numStep;
if (pySim->state == StateStopped) {
pySim->state = StateRunning;
pySim->sigPause = 1;
pySim->running = true;
pySim->simThread = new std::thread(simThreadFunction, pySim);
} else if (pySim->state == StatePaused) {
pySim->sigPause = 1;
pySim->cond->notify_one();
} else if (pySim->state == StateDone) {
PyErr_SetString(PyExc_SystemError, "Simulation already finished");
return nullptr;
} else {
PyErr_SetString(PyExc_SystemError, "Simulation currently running");
return nullptr;
}
while (pySim->numStep == oldStep)
pySim->cond->wait(lk);
Py_INCREF(Py_None);
return Py_None;
}
PyObject* PySimulation::stop(PyObject *self, PyObject *args) {
PySimulation* pySim = (PySimulation*) self;
std::unique_lock<std::mutex> lk(*pySim->mut);
pySim->running = false;
while (pySim->state == StateRunning)
pySim->cond->wait(lk);
Py_INCREF(Py_None);
return Py_None;
}
PyObject* PySimulation::pause(PyObject *self, PyObject *args) {
PySimulation *pySim = (PySimulation*) self;
std::unique_lock<std::mutex> lk(*pySim->mut);
if (pySim->state != StateRunning) {
PyErr_SetString(PyExc_SystemError, "Simulation not currently running");
return nullptr;
}
pySim->sigPause = 1;
pySim->cond->notify_one();
while (pySim->state == StateRunning)
pySim->cond->wait(lk);
Py_INCREF(Py_None);
return Py_None;
}
PyObject* PySimulation::wait(PyObject *self, PyObject *args) {
PySimulation *pySim = (PySimulation*) self;
std::unique_lock<std::mutex> lk(*pySim->mut);
if (pySim->state == StateDone) {
Py_INCREF(Py_None);
return Py_None;
} else if (pySim->state == StateStopped) {
PyErr_SetString(PyExc_SystemError, "Simulation not currently running");
return nullptr;
} else if (pySim->state == StatePaused) {
PyErr_SetString(PyExc_SystemError, "Simulation currently paused");
return nullptr;
}
while (pySim->state == StateRunning)
pySim->cond->wait(lk);
Py_INCREF(Py_None);
return Py_None;
}
#ifndef PYSIMULATION_H
#define PYSIMULATION_H
#include <Python.h>
#include <atomic>
#include <condition_variable>
#include <mutex>
#include <thread>
#include <vector>
#include "Components/BaseComponent.h"
#include "Simulation.h"
namespace DPsim {
enum SimState {
StateStopped = 0,
StateRunning,
StatePaused,
StateDone
};
struct PySimulation {
PyObject_HEAD
Simulation *sim;
Logger *log; // TODO other loggers
std::condition_variable *cond;
std::mutex *mut;
std::atomic_bool running;
std::atomic_int sigPause, numStep;
std::thread *simThread;
SimState state;
// Function executed by the simulation thread
static void simThreadFunction(PySimulation* pySim);
// The Python API has no notion of C++ classes and methods, so the methods
// that can be called from Python are static.
//
// Helper methods for memory management / initialization etc.
static PyObject* newfunc(PyTypeObject* type, PyObject *args, PyObject *kwds);
static int init(PySimulation* self, PyObject *args, PyObject *kwds);
static void dealloc(PySimulation*);
// Methods that are actually available from Python
static PyObject* start(PyObject *self, PyObject *args);
static PyObject* step(PyObject *self, PyObject *args);
static PyObject* stop(PyObject *self, PyObject *args);
static PyObject* pause(PyObject *self, PyObject *args);
static PyObject* wait(PyObject *self, PyObject *args);
};
static PyMethodDef PySimulation_methods[] = {
{"start", PySimulation::start, METH_VARARGS, "Start the simulation, or resume if it is paused."},
{"step", PySimulation::step, METH_VARARGS, "Perform a single simulation step."},
{"stop", PySimulation::stop, METH_VARARGS, "Cancel the running simulation."},
{"pause", PySimulation::pause, METH_VARARGS, "Pause the already running simulation."},
{"wait", PySimulation::wait, METH_VARARGS, "Wait for the simulation to finish."},
{NULL, NULL, 0, NULL}
};
static PyTypeObject PySimulationType = {
PyVarObject_HEAD_INIT(NULL, 0)
"dpsim.Simulation", /* tp_name */
sizeof(PySimulation), /* tp_basicsize */
0, /* tp_itemsize */
(destructor)PySimulation::dealloc, /* tp_dealloc */
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_reserved */
0, /* tp_repr */
0, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
0, /* tp_hash */
0, /* tp_call */
0, /* tp_str */
0, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT |
Py_TPFLAGS_BASETYPE, /* tp_flags */
"A single simulation.", /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
PySimulation_methods, /* tp_methods */
0, /* tp_members */
0, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
(initproc)PySimulation::init, /* tp_init */
0, /* tp_alloc */
PySimulation::newfunc, /* tp_new */
};
static std::vector<BaseComponent*> components;
};
#endif
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment