python interpreter refactored to a task queue

This commit is contained in:
tmj-fstate
2018-09-28 19:16:41 +02:00
parent 308bea337d
commit 7c60829f54
10 changed files with 485 additions and 670 deletions

View File

@@ -70,4 +70,6 @@ enum class TCommandType
cm_Command // komenda pobierana z komórki
};
using material_handle = int;
#endif

817
PyInt.cpp
View File

@@ -1,24 +1,56 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "PyInt.h"
#include "globals.h"
#include "parser.h"
#include "application.h"
#include "renderer.h"
#include "Model3d.h"
#include "Train.h"
#include "Logs.h"
TPythonInterpreter *TPythonInterpreter::_instance = NULL;
void render_task::run() {
// call the renderer
auto *output { PyObject_CallMethod( m_renderer, "render", "O", m_input ) };
Py_DECREF( m_input );
//#define _PY_INT_MORE_LOG
if( output != nullptr ) {
auto *outputwidth { PyObject_CallMethod( m_renderer, "get_width", nullptr ) };
auto *outputheight { PyObject_CallMethod( m_renderer, "get_height", nullptr ) };
// upload texture data
if( ( outputwidth != nullptr )
&& ( outputheight != nullptr ) ) {
#ifdef __GNUC__
#pragma GCC diagnostic ignored "-Wwrite-strings"
#endif
GfxRenderer.Bind_Material( m_target );
// setup texture parameters
::glTexParameteri( GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE );
::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR );
::glTexEnvf( GL_TEXTURE_FILTER_CONTROL, GL_TEXTURE_LOD_BIAS, -1.0 );
// build texture
::glTexImage2D(
GL_TEXTURE_2D, 0,
GL_RGBA8,
PyInt_AsLong( outputwidth ), PyInt_AsLong( outputheight ), 0,
GL_RGB, GL_UNSIGNED_BYTE, reinterpret_cast<GLubyte const *>( PyString_AsString( output ) ) );
}
Py_DECREF( outputheight );
Py_DECREF( outputwidth );
Py_DECREF( output );
}
// clean up after yourself
delete this;
}
// initializes the module. returns true on success
auto python_taskqueue::init() -> bool {
TPythonInterpreter::TPythonInterpreter()
{
WriteLog("Loading Python ...");
#ifdef _WIN32
if (sizeof(void*) == 8)
Py_SetPythonHome("python64");
@@ -31,568 +63,253 @@ TPythonInterpreter::TPythonInterpreter()
Py_SetPythonHome("linuxpython");
#endif
Py_Initialize();
_main = PyImport_ImportModule("__main__");
if (_main == NULL)
{
WriteLog("Cannot import Python module __main__");
PyEval_InitThreads();
// save a pointer to the main PyThreadState object
m_mainthread = PyThreadState_Get();
// do the setup work while we hold the lock
m_main = PyImport_ImportModule("__main__");
if (m_main == nullptr) {
ErrorLog( "Python Interpreter: __main__ module is missing" );
goto release_and_exit;
}
PyObject *cStringModule = PyImport_ImportModule("cStringIO");
_stdErr = NULL;
if (cStringModule == NULL)
return;
PyObject *cStringClassName = PyObject_GetAttrString(cStringModule, "StringIO");
if (cStringClassName == NULL)
return;
PyObject *cString = PyObject_CallObject(cStringClassName, NULL);
if (cString == NULL)
return;
if (PySys_SetObject("stderr", cString) != 0)
return;
_stdErr = cString;
auto *stringiomodule { PyImport_ImportModule( "cStringIO" ) };
auto *stringioclassname { (
stringiomodule != nullptr ?
PyObject_GetAttrString( stringiomodule, "StringIO" ) :
nullptr ) };
auto *stringioobject { (
stringioclassname != nullptr ?
PyObject_CallObject( stringioclassname, nullptr ) :
nullptr ) };
m_error = { (
stringioobject == nullptr ? nullptr :
PySys_SetObject( "stderr", stringioobject ) != 0 ? nullptr :
stringioobject ) };
if( m_error == nullptr ) { goto release_and_exit; }
if( false == run_file( "abstractscreenrenderer" ) ) { goto release_and_exit; }
// release the lock
PyEval_ReleaseLock();
WriteLog( "Python Interpreter setup complete" );
// init workers
for( auto &worker : m_workers ) {
auto *openglcontextwindow { Application.window( -1 ) };
worker =
std::make_unique<std::thread>(
&python_taskqueue::run, this,
openglcontextwindow, std::ref( m_tasks ), std::ref( m_condition ), std::ref( m_exit ) );
if( worker == nullptr ) { return false; }
}
return true;
release_and_exit:
PyEval_ReleaseLock();
return false;
}
TPythonInterpreter *TPythonInterpreter::getInstance()
{
if (!_instance)
{
_instance = new TPythonInterpreter();
// shuts down the module
void python_taskqueue::exit() {
// let the workers know we're done with them
m_exit = true;
m_condition.notify_all();
// let them free up their shit before we proceed
for( auto const &worker : m_workers ) {
worker->join();
}
return _instance;
// get rid of the leftover tasks
// with the workers dead we don't have to worry about concurrent access anymore
for( auto *task : m_tasks.data ) {
delete task;
}
// take a bow
PyEval_AcquireLock();
PyThreadState_Swap( m_mainthread );
Py_Finalize();
}
void
TPythonInterpreter::killInstance() {
// adds specified task along with provided collection of data to the work queue. returns true on success
auto python_taskqueue::insert( task_request const &Task ) -> bool {
delete _instance;
}
if( ( Task.renderer.empty() )
|| ( Task.input == nullptr )
|| ( Task.target == null_handle ) ) { return false; }
bool TPythonInterpreter::loadClassFile( std::string const &lookupPath, std::string const &className )
{
std::set<std::string>::const_iterator it = _classes.find(className);
if (it == _classes.end())
auto *renderer { fetch_renderer( Task.renderer ) };
if( renderer == nullptr ) { return false; }
// acquire a lock on the task queue and add a new task
{
FILE *sourceFile = _getFile(lookupPath, className);
if (sourceFile != nullptr)
{
fseek(sourceFile, 0, SEEK_END);
auto const fsize = ftell(sourceFile);
char *buffer = (char *)calloc(fsize + 1, sizeof(char));
fseek(sourceFile, 0, SEEK_SET);
auto const freaded = fread(buffer, sizeof(char), fsize, sourceFile);
buffer[freaded] = 0; // z jakiegos powodu czytamy troche mniej i trzczeba dodac konczace
// zero do bufora (mimo ze calloc teoretycznie powiniene zwrocic
// wyzerowana pamiec)
#ifdef _PY_INT_MORE_LOG
char buf[255];
sprintf(buf, "readed %d / %d characters for %s", freaded, fsize, className);
WriteLog(buf);
#endif // _PY_INT_MORE_LOG
fclose(sourceFile);
if (PyRun_SimpleString(buffer) != 0)
{
handleError();
return false;
}
_classes.insert( className );
/*
char *classNameToRemember = (char *)calloc(strlen(className) + 1, sizeof(char));
strcpy(classNameToRemember, className);
_classes.insert(classNameToRemember);
*/
free(buffer);
return true;
}
return false;
std::lock_guard<std::mutex> lock( m_tasks.mutex );
m_tasks.data.emplace_back( new render_task( renderer, Task.input, Task.target ) );
}
// potentially wake a worker to handle the new task
m_condition.notify_one();
// all done
return true;
}
PyObject *TPythonInterpreter::newClass( std::string const &className )
{
return newClass(className, NULL);
// executes python script stored in specified file. returns true on success
auto python_taskqueue::run_file( std::string const &File, std::string const &Path ) -> bool {
auto const lookup { FileExists( { Path + File, "python/local/" + File }, { ".py" } ) };
if( lookup.first.empty() ) { return false; }
std::ifstream inputfile { lookup.first + lookup.second };
std::string input;
input.assign( std::istreambuf_iterator<char>( inputfile ), std::istreambuf_iterator<char>() );
if( PyRun_SimpleString( input.c_str() ) != 0 ) {
error();
return false;
}
return true;
}
FILE *TPythonInterpreter::_getFile( std::string const &lookupPath, std::string const &className )
{
if( false == lookupPath.empty() ) {
std::string const sourcefilepath = lookupPath + className + ".py";
FILE *file = fopen( sourcefilepath.c_str(), "r" );
#ifdef _PY_INT_MORE_LOG
WriteLog( sourceFilePath );
#endif // _PY_INT_MORE_LOG
if( nullptr != file ) { return file; }
}
std::string sourcefilepath = "python/local/" + className + ".py";
FILE *file = fopen( sourcefilepath.c_str(), "r" );
#ifdef _PY_INT_MORE_LOG
WriteLog( sourceFilePath );
#endif // _PY_INT_MORE_LOG
return file; // either the file, or a nullptr on fail
/*
char *sourceFilePath;
if (lookupPath != NULL)
{
sourceFilePath = (char *)calloc(strlen(lookupPath) + strlen(className) + 4, sizeof(char));
strcat(sourceFilePath, lookupPath);
strcat(sourceFilePath, className);
strcat(sourceFilePath, ".py");
auto python_taskqueue::fetch_renderer( std::string const Renderer ) ->PyObject * {
FILE *file = fopen(sourceFilePath, "r");
#ifdef _PY_INT_MORE_LOG
WriteLog(sourceFilePath);
#endif // _PY_INT_MORE_LOG
free(sourceFilePath);
if (file != NULL)
{
return file;
auto const lookup { m_renderers.find( Renderer ) };
if( lookup != std::end( m_renderers ) ) {
return lookup->second;
}
// try to load specified renderer class
auto const path { substr_path( Renderer ) };
auto const file { Renderer.substr( path.size() ) };
PyObject *renderer { nullptr };
if( m_main == nullptr ) {
ErrorLog( "Python Renderer: __main__ module is missing" );
goto cache_and_return;
}
PyEval_AcquireLock();
{
PyObject *rendererarguments{ nullptr };
if( false == run_file( file, path ) ) {
goto cache_and_return;
}
auto *renderername{ PyObject_GetAttrString( m_main, file.c_str() ) };
if( renderername == nullptr ) {
ErrorLog( "Python Renderer: class \"" + file + "\" not defined" );
goto cache_and_return;
}
rendererarguments = Py_BuildValue( "(s)", path.c_str() );
if( rendererarguments == nullptr ) {
ErrorLog( "Python Renderer: failed to create initialization arguments" );
goto cache_and_return;
}
renderer = PyObject_CallObject( renderername, rendererarguments );
if( PyErr_Occurred() != nullptr ) {
error();
renderer = nullptr;
}
cache_and_return:
// clean up after yourself
if( rendererarguments != nullptr ) {
Py_DECREF( rendererarguments );
}
}
char *basePath = "python/local/";
sourceFilePath = (char *)calloc(strlen(basePath) + strlen(className) + 4, sizeof(char));
strcat(sourceFilePath, basePath);
strcat(sourceFilePath, className);
strcat(sourceFilePath, ".py");
FILE *file = fopen(sourceFilePath, "r");
#ifdef _PY_INT_MORE_LOG
WriteLog(sourceFilePath);
#endif // _PY_INT_MORE_LOG
free(sourceFilePath);
if (file != NULL)
{
return file;
}
return NULL;
*/
PyEval_ReleaseLock();
// cache the failures as well so we don't try again on subsequent requests
m_renderers.emplace( Renderer, renderer );
return renderer;
}
void TPythonInterpreter::handleError()
{
#ifdef _PY_INT_MORE_LOG
WriteLog("Python Error occured");
#endif // _PY_INT_MORE_LOG
if (_stdErr != NULL)
{ // std err pythona jest buforowane
void python_taskqueue::run( GLFWwindow *Context, rendertask_sequence &Tasks, threading::condition_variable &Condition, std::atomic<bool> &Exit ) {
glfwMakeContextCurrent( Context );
// create a state object for this thread
PyEval_AcquireLock();
auto *threadstate { PyThreadState_New( m_mainthread->interp ) };
PyEval_ReleaseLock();
render_task *task { nullptr };
while( false == Exit.load() ) {
// regardless of the reason we woke up prime the spurious wakeup flag for the next time
Condition.spurious( true );
// keep working as long as there's any scheduled tasks
do {
task = nullptr;
// acquire a lock on the task queue and potentially grab a task from it
{
std::lock_guard<std::mutex> lock( Tasks.mutex );
if( false == Tasks.data.empty() ) {
// fifo
task = Tasks.data.front();
Tasks.data.pop_front();
}
}
if( task != nullptr ) {
// swap in my thread state
PyEval_AcquireLock();
PyThreadState_Swap( threadstate );
// execute python code
task->run();
error();
// clear the thread state
PyThreadState_Swap( nullptr );
PyEval_ReleaseLock();
}
// TBD, TODO: add some idle time between tasks in case we're on a single thread cpu?
} while( task != nullptr );
// if there's nothing left to do wait until there is
// but check every now and then on your own to minimize potential deadlock situations
Condition.wait_for( std::chrono::seconds( 5 ) );
}
// clean up thread state data
PyEval_AcquireLock();
PyThreadState_Swap( nullptr );
PyThreadState_Clear( threadstate );
PyThreadState_Delete( threadstate );
PyEval_ReleaseLock();
}
void
python_taskqueue::error() {
if( PyErr_Occurred() == nullptr ) { return; }
if( m_error != nullptr ) {
// std err pythona jest buforowane
PyErr_Print();
PyObject *bufferContent = PyObject_CallMethod(_stdErr, "getvalue", NULL);
PyObject_CallMethod(_stdErr, "truncate", "i", 0); // czyscimy bufor na kolejne bledy
WriteLog(PyString_AsString(bufferContent));
auto *errortext { PyObject_CallMethod( m_error, "getvalue", nullptr ) };
ErrorLog( PyString_AsString( errortext ) );
// czyscimy bufor na kolejne bledy
PyObject_CallMethod( m_error, "truncate", "i", 0 );
}
else
{ // nie dziala buffor pythona
if (PyErr_Occurred() != NULL)
{
PyObject *ptype, *pvalue, *ptraceback;
PyErr_Fetch(&ptype, &pvalue, &ptraceback);
if (ptype == NULL)
{
WriteLog("Don't konw how to handle NULL exception");
}
PyErr_NormalizeException(&ptype, &pvalue, &ptraceback);
if (ptype == NULL)
{
WriteLog("Don't konw how to handle NULL exception");
}
PyObject *pStrType = PyObject_Str(ptype);
if (pStrType != NULL)
{
WriteLog(PyString_AsString(pStrType));
}
WriteLog(PyString_AsString(pvalue));
PyObject *pStrTraceback = PyObject_Str(ptraceback);
if (pStrTraceback != NULL)
{
WriteLog(PyString_AsString(pStrTraceback));
}
else
{
WriteLog("Python Traceback cannot be shown");
}
else {
// nie dziala buffor pythona
PyObject *type, *value, *traceback;
PyErr_Fetch( &type, &value, &traceback );
if( type == nullptr ) {
ErrorLog( "Python Interpreter: don't know how to handle null exception" );
}
else
{
#ifdef _PY_INT_MORE_LOG
WriteLog("Called python error handler when no error occured!");
#endif // _PY_INT_MORE_LOG
PyErr_NormalizeException( &type, &value, &traceback );
if( type == nullptr ) {
ErrorLog( "Python Interpreter: don't know how to handle null exception" );
}
auto *typetext { PyObject_Str( type ) };
if( typetext != nullptr ) {
ErrorLog( PyString_AsString( typetext ) );
}
if( value != nullptr ) {
ErrorLog( PyString_AsString( value ) );
}
auto *tracebacktext { PyObject_Str( traceback ) };
if( tracebacktext != nullptr ) {
WriteLog( PyString_AsString( tracebacktext ) );
}
else {
WriteLog( "Python Interpreter: failed to retrieve the stack traceback" );
}
}
}
PyObject *TPythonInterpreter::newClass(std::string const &className, PyObject *argsTuple)
{
if (_main == NULL)
{
WriteLog("main turned into null");
return NULL;
}
PyObject *classNameObj = PyObject_GetAttrString(_main, className.c_str());
if (classNameObj == NULL)
{
#ifdef _PY_INT_MORE_LOG
char buf[255];
sprintf(buf, "Python class %s not defined!", className);
WriteLog(buf);
#endif // _PY_INT_MORE_LOG
return NULL;
}
PyObject *object = PyObject_CallObject(classNameObj, argsTuple);
if (PyErr_Occurred() != NULL)
{
handleError();
return NULL;
}
return object;
}
TPythonScreenRenderer::TPythonScreenRenderer(int textureId, PyObject *renderer)
{
_textureId = textureId;
_pyRenderer = renderer;
}
void TPythonScreenRenderer::updateTexture()
{
int width, height;
if (_pyWidth == NULL || _pyHeight == NULL)
{
WriteLog("Unknown python texture size!");
return;
}
width = PyInt_AsLong(_pyWidth);
height = PyInt_AsLong(_pyHeight);
if (_pyTexture != NULL)
{
char *textureData = PyString_AsString(_pyTexture);
if (textureData != NULL)
{
#ifdef _PY_INT_MORE_LOG
char buff[255];
sprintf(buff, "Sending texture id: %d w: %d h: %d", _textureId, width, height);
WriteLog(buff);
#endif // _PY_INT_MORE_LOG
/*
glPixelStorei( GL_UNPACK_ALIGNMENT, 1 );
glPixelStorei( GL_PACK_ALIGNMENT, 1 );
*/
GfxRenderer.Bind_Material(_textureId);
// setup texture parameters
glTexParameteri( GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR );
glTexEnvf( GL_TEXTURE_FILTER_CONTROL, GL_TEXTURE_LOD_BIAS, -1.0 );
// build texture
glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, textureData );
#ifdef _PY_INT_MORE_LOG
GLenum status = glGetError();
switch (status)
{
case GL_INVALID_ENUM:
WriteLog("An unacceptable value is specified for an enumerated argument. The "
"offending function is ignored, having no side effect other than to set "
"the error flag.");
break;
case GL_INVALID_VALUE:
WriteLog("A numeric argument is out of range. The offending function is ignored, "
"having no side effect other than to set the error flag.");
break;
case GL_INVALID_OPERATION:
WriteLog("The specified operation is not allowed in the current state. The "
"offending function is ignored, having no side effect other than to set "
"the error flag.");
break;
case GL_NO_ERROR:
WriteLog("No error has been recorded. The value of this symbolic constant is "
"guaranteed to be zero.");
break;
case GL_STACK_OVERFLOW:
WriteLog("This function would cause a stack overflow. The offending function is "
"ignored, having no side effect other than to set the error flag.");
break;
case GL_STACK_UNDERFLOW:
WriteLog("This function would cause a stack underflow. The offending function is "
"ignored, having no side effect other than to set the error flag.");
break;
case GL_OUT_OF_MEMORY:
WriteLog("There is not enough memory left to execute the function. The state of "
"OpenGL is undefined, except for the state of the error flags, after this "
"error is recorded.");
break;
};
#endif // _PY_INT_MORE_LOG
}
else
{
WriteLog("RAW python texture data is NULL!");
}
}
else
{
WriteLog("Python texture object is NULL!");
}
}
void TPythonScreenRenderer::render(PyObject *trainState)
{
#ifdef _PY_INT_MORE_LOG
WriteLog("Python rendering texture ...");
#endif // _PY_INT_MORE_LOG
_pyTexture = PyObject_CallMethod(_pyRenderer, "render", "O", trainState);
if (_pyTexture == NULL)
{
TPythonInterpreter::getInstance()->handleError();
}
else
{
_pyWidth = PyObject_CallMethod(_pyRenderer, "get_width", NULL);
if (_pyWidth == NULL)
{
TPythonInterpreter::getInstance()->handleError();
}
_pyHeight = PyObject_CallMethod(_pyRenderer, "get_height", NULL);
if (_pyHeight == NULL)
{
TPythonInterpreter::getInstance()->handleError();
}
}
}
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif
TPythonScreenRenderer::~TPythonScreenRenderer()
{
#ifdef _PY_INT_MORE_LOG
WriteLog("PythonScreenRenderer descturctor called");
#endif // _PY_INT_MORE_LOG
if (_pyRenderer != NULL)
{
Py_CLEAR(_pyRenderer);
}
cleanup();
#ifdef _PY_INT_MORE_LOG
WriteLog("PythonScreenRenderer desctructor finished");
#endif // _PY_INT_MORE_LOG
}
void TPythonScreenRenderer::cleanup()
{
if (_pyTexture != NULL)
{
Py_CLEAR(_pyTexture);
_pyTexture = NULL;
}
if (_pyWidth != NULL)
{
Py_CLEAR(_pyWidth);
_pyWidth = NULL;
}
if (_pyHeight != NULL)
{
Py_CLEAR(_pyHeight);
_pyHeight = NULL;
}
}
void TPythonScreens::reset(void *train)
{
_terminationFlag = true;
if (_thread != NULL)
{
// WriteLog("Awaiting python thread to end");
_thread->join();
delete _thread;
_thread = nullptr;
}
_terminationFlag = false;
_cleanupReadyFlag = false;
_renderReadyFlag = false;
for (std::vector<TPythonScreenRenderer *>::iterator i = _screens.begin(); i != _screens.end();
++i)
{
delete *i;
}
#ifdef _PY_INT_MORE_LOG
WriteLog("Clearing renderer vector");
#endif // _PY_INT_MORE_LOG
_screens.clear();
_train = train;
}
void TPythonScreens::init(cParser &parser, TModel3d *model, std::string const &name, int const cab)
{
std::string asSubModelName, asPyClassName;
parser.getTokens( 2, false );
parser
>> asSubModelName
>> asPyClassName;
std::string subModelName = ToLower( asSubModelName );
std::string pyClassName = ToLower( asPyClassName );
TSubModel *subModel = model->GetFromName(subModelName);
if (subModel == NULL)
{
WriteLog( "Python Screen: submodel " + subModelName + " not found - Ignoring screen" );
return; // nie ma takiego sub modelu w danej kabinie pomijamy
}
auto textureId = subModel->GetMaterial();
if (textureId <= 0)
{
WriteLog( "Python Screen: invalid texture id " + std::to_string(textureId) + " - Ignoring screen" );
return; // sub model nie posiada tekstury lub tekstura wymienna - nie obslugiwana
}
TPythonInterpreter *python = TPythonInterpreter::getInstance();
python->loadClassFile(_lookupPath, pyClassName);
PyObject *args = Py_BuildValue("(ssi)", _lookupPath.c_str(), name.c_str(), cab);
if (args == NULL)
{
WriteLog("Python Screen: cannot create __init__ arguments");
return;
}
PyObject *pyRenderer = python->newClass(pyClassName, args);
Py_CLEAR(args);
if (pyRenderer == NULL)
{
WriteLog( "Python Screen: null renderer for " + pyClassName + " - Ignoring screen" );
return; // nie mozna utworzyc obiektu Pythonowego
}
m_updaterate = Global.PythonScreenUpdateRate;
TPythonScreenRenderer *renderer = new TPythonScreenRenderer(textureId, pyRenderer);
_screens.push_back(renderer);
WriteLog( "Created python screen " + pyClassName + " on submodel " + subModelName + " (" + std::to_string(textureId) + ")" );
}
void TPythonScreens::update()
{
if (!_renderReadyFlag)
{
return;
}
_renderReadyFlag = false;
for (std::vector<TPythonScreenRenderer *>::iterator i = _screens.begin(); i != _screens.end();
++i)
{
(*i)->updateTexture();
}
_cleanupReadyFlag = true;
}
void TPythonScreens::setLookupPath(std::string const &path)
{
_lookupPath = path;
replace_slashes( _lookupPath );
}
TPythonScreens::TPythonScreens()
{
TPythonInterpreter::getInstance()->loadClassFile("", "abstractscreenrenderer");
}
TPythonScreens::~TPythonScreens()
{
#ifdef _PY_INT_MORE_LOG
WriteLog("Called python sceeens destructor");
#endif // _PY_INT_MORE_LOG
reset(NULL);
/*
if (_lookupPath != NULL)
{
#ifdef _PY_INT_MORE_LOG
WriteLog("Freeing lookup path");
#endif // _PY_INT_MORE_LOG
free(_lookupPath);
}
*/
}
void TPythonScreens::run()
{
while (1)
{
m_updatestopwatch.start();
if (_terminationFlag)
{
return;
}
TTrain *train = (TTrain *)_train;
_trainState = train->GetTrainState();
if (_terminationFlag)
{
_freeTrainState();
return;
}
for (std::vector<TPythonScreenRenderer *>::iterator i = _screens.begin();
i != _screens.end(); ++i)
{
(*i)->render(_trainState);
}
_freeTrainState();
if (_terminationFlag)
{
_cleanup();
return;
}
_renderReadyFlag = true;
m_updatestopwatch.stop();
while (!_cleanupReadyFlag && !_terminationFlag)
{
auto const sleeptime {
std::max(
100,
m_updaterate - static_cast<int>( m_updatestopwatch.average() ) ) };
#ifdef _WIN32
Sleep( sleeptime );
#elif __linux__
usleep( sleeptime * 1000 );
#endif
}
if (_terminationFlag)
{
return;
}
_cleanup();
}
}
void TPythonScreens::finish()
{
// nothing to do here, proper clean up takes place afterwards
}
void ScreenRendererThread(TPythonScreens* renderer)
{
renderer->run();
renderer->finish();
#ifdef _PY_INT_MORE_LOG
WriteLog("Python Screen Renderer Thread Ends");
#endif // _PY_INT_MORE_LOG
}
void TPythonScreens::start()
{
if (_screens.size() > 0)
{
_thread = new std::thread(ScreenRendererThread, this);
}
}
void TPythonScreens::_cleanup()
{
_cleanupReadyFlag = false;
for (std::vector<TPythonScreenRenderer *>::iterator i = _screens.begin(); i != _screens.end();
++i)
{
(*i)->cleanup();
}
}
void TPythonScreens::_freeTrainState()
{
if (_trainState != NULL)
{
PyDict_Clear(_trainState);
Py_CLEAR(_trainState);
_trainState = NULL;
}
}

137
PyInt.h
View File

@@ -1,13 +1,13 @@
#ifndef PyIntH
#define PyIntH
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#ifdef _POSIX_C_SOURCE
#undef _POSIX_C_SOURCE
#endif
#ifdef _XOPEN_SOURCE
#undef _XOPEN_SOURCE
#endif
#pragma once
#ifdef _DEBUG
#undef _DEBUG // bez tego macra Py_DECREF powoduja problemy przy linkowaniu
@@ -17,78 +17,69 @@
#include "Python.h"
#endif
#include "Classes.h"
#include "timer.h"
#define PyGetFloat(param) PyFloat_FromDouble(param >= 0 ? param : -param)
#define PyGetFloatS(param) PyFloat_FromDouble(param)
#define PyGetInt(param) PyInt_FromLong(param)
#define PyGetFloatS(param) PyFloat_FromDouble(param)
#define PyGetBool(param) param ? Py_True : Py_False
#define PyGetString(param) PyString_FromString(param)
class TPythonInterpreter
{
protected:
TPythonInterpreter();
~TPythonInterpreter() {}
static TPythonInterpreter *_instance;
std::set<std::string> _classes;
PyObject *_main;
PyObject *_stdErr;
FILE *_getFile( std::string const &lookupPath, std::string const &className );
// TODO: extract common base and inherit specialization from it
class render_task {
public:
static TPythonInterpreter *getInstance();
static void killInstance();
bool loadClassFile( std::string const &lookupPath, std::string const &className );
PyObject *newClass( std::string const &className );
PyObject *newClass( std::string const &className, PyObject *argsTuple );
void handleError();
};
class TPythonScreenRenderer
{
protected:
PyObject *_pyRenderer;
PyObject *_pyTexture;
int _textureId;
PyObject *_pyWidth;
PyObject *_pyHeight;
public:
TPythonScreenRenderer(int textureId, PyObject *renderer);
~TPythonScreenRenderer();
void render(PyObject *trainState);
void cleanup();
void updateTexture();
};
class TPythonScreens
{
protected:
bool _cleanupReadyFlag{ false };
bool _renderReadyFlag{ false };
bool _terminationFlag{ false };
std::thread *_thread{ nullptr };
std::vector<TPythonScreenRenderer *> _screens;
std::string _lookupPath;
void *_train;
void _cleanup();
void _freeTrainState();
PyObject *_trainState;
int m_updaterate { 200 };
Timer::stopwatch m_updatestopwatch;
public:
void reset(void *train);
void setLookupPath(std::string const &path);
void init(cParser &parser, TModel3d *model, std::string const &name, int const cab);
void update();
TPythonScreens();
~TPythonScreens();
public:
// constructors
render_task( PyObject *Renderer, PyObject *Input, material_handle Target ) :
m_renderer( Renderer ), m_input( Input ), m_target( Target )
{}
// methods
void run();
void start();
void finish();
private:
// members
PyObject *m_renderer {nullptr};
PyObject *m_input { nullptr };
material_handle m_target { null_handle };
};
#endif // PyIntH
class python_taskqueue {
public:
// types
struct task_request {
std::string const &renderer;
PyObject *input;
material_handle target;
};
// constructors
python_taskqueue() = default;
// methods
// initializes the module. returns true on success
auto init() -> bool;
// shuts down the module
void exit();
// adds specified task along with provided collection of data to the work queue. returns true on success
auto insert( task_request const &Task ) -> bool;
// executes python script stored in specified file. returns true on success
auto run_file( std::string const &File, std::string const &Path = "" ) -> bool;
private:
// types
static int const WORKERCOUNT { 1 };
using worker_array = std::array<std::unique_ptr<std::thread>, WORKERCOUNT >;
using rendertask_sequence = threading::lockable< std::deque<render_task *> >;
// methods
auto fetch_renderer( std::string const Renderer ) -> PyObject *;
void run( GLFWwindow *Context, rendertask_sequence &Tasks, threading::condition_variable &Condition, std::atomic<bool> &Exit );
void error();
// members
PyObject *m_main { nullptr };
PyObject *m_error { nullptr };
PyThreadState *m_mainthread{ nullptr };
worker_array m_workers;
threading::condition_variable m_condition; // wakes up the workers
std::atomic<bool> m_exit { false }; // signals the workers to quit
std::unordered_map<std::string, PyObject *> m_renderers; // cache of python classes
rendertask_sequence m_tasks;
};

View File

@@ -9,8 +9,6 @@ http://mozilla.org/MPL/2.0/.
#pragma once
int const null_handle = 0;
enum class resource_state {
none,
loading,

View File

@@ -28,6 +28,7 @@ http://mozilla.org/MPL/2.0/.
#include "dynobj.h"
#include "mtable.h"
#include "Console.h"
#include "application.h"
namespace input {
@@ -449,6 +450,7 @@ PyObject *TTrain::GetTrainState() {
return nullptr;
}
PyDict_SetItemString( dict, "name", PyGetString( DynamicObject->asName.c_str() ) );
PyDict_SetItemString( dict, "cab", PyGetInt( mover->ActiveCab ) );
// basic systems state data
PyDict_SetItemString( dict, "battery", PyGetBool( mvControlled->Battery ) );
@@ -469,7 +471,7 @@ PyObject *TTrain::GetTrainState() {
PyDict_SetItemString( dict, "dir_brake", PyGetBool( bEP ) );
bool bPN;
if( ( typeid( *mvControlled->Hamulec ) == typeid( TLSt ) )
|| ( typeid( *mvControlled->Hamulec ) == typeid( TEStED ) ) ) {
|| ( typeid( *mvControlled->Hamulec ) == typeid( TEStED ) ) ) {
TBrake* temp_ham = mvControlled->Hamulec.get();
bPN = ( static_cast<TLSt*>( temp_ham )->GetEDBCP() > 0.2 );
@@ -4682,7 +4684,6 @@ bool TTrain::Update( double const Deltatime )
}
}
tor = DynamicObject->GetTrack(); // McZapkie-180203
// McZapkie: predkosc wyswietlana na tachometrze brana jest z obrotow kol
auto const maxtacho { 3.0 };
fTachoVelocity = static_cast<float>( std::min( std::abs(11.31 * mvControlled->WheelDiameter * mvControlled->nrot), mvControlled->Vmax * 1.05) );
@@ -5606,7 +5607,6 @@ bool TTrain::Update( double const Deltatime )
ggFuelPumpButton.Update();
ggOilPumpButton.Update();
//------
pyScreens.update();
}
// wyprowadzenie sygnałów dla haslera na PoKeys (zaznaczanie na taśmie)
btHaslerBrakes.Turn(DynamicObject->MoverParameters->BrakePress > 0.4); // ciśnienie w cylindrach
@@ -5676,8 +5676,8 @@ bool TTrain::Update( double const Deltatime )
}
}
/*
// NOTE: disabled while switch state isn't preserved while moving between compartments
// check whether we should raise the pantographs, based on volume in pantograph tank
// NOTE: disabled while switch state isn't preserved while moving between compartments
if( mvControlled->PantPress > (
mvControlled->TrainType == dt_EZT ?
2.4 :
@@ -5692,7 +5692,15 @@ bool TTrain::Update( double const Deltatime )
}
}
*/
// screens
fScreenTimer += Deltatime;
if( ( fScreenTimer > Global.PythonScreenUpdateRate * 0.001f )
&& ( false == FreeFlyModeFlag ) ) { // don't bother if we're outside
fScreenTimer = 0.f;
for( auto const &screen : m_screens ) {
Application.request( { screen.first, GetTrainState(), screen.second } );
}
}
// sounds
update_sounds( Deltatime );
@@ -5853,7 +5861,6 @@ TTrain::update_sounds( double const Deltatime ) {
dsbSlipAlarm.stop();
}
}
/*
// szum w czasie jazdy
if( ( false == FreeFlyModeFlag )
&& ( false == Global.CabWindowOpen )
@@ -5865,7 +5872,6 @@ TTrain::update_sounds( double const Deltatime ) {
// don't play the optional ending sound if the listener switches views
rsRunningNoise.stop( true == FreeFlyModeFlag );
}
*/
// hunting oscillation noise
if( ( false == FreeFlyModeFlag )
&& ( false == Global.CabWindowOpen )
@@ -6255,6 +6261,8 @@ bool TTrain::LoadMMediaFile(std::string const &asFileName)
bool TTrain::InitializeCab(int NewCabNo, std::string const &asFileName)
{
m_controlmapper.clear();
// clear python screens
m_screens.clear();
// reset sound positions and owner
auto const nullvector { glm::vec3() };
std::vector<sound_source *> sounds = {
@@ -6272,9 +6280,6 @@ bool TTrain::InitializeCab(int NewCabNo, std::string const &asFileName)
pMechViewAngle = { 0.0, 0.0 };
Global.pCamera.Pitch = pMechViewAngle.x;
Global.pCamera.Yaw = pMechViewAngle.y;
pyScreens.reset(this);
pyScreens.setLookupPath(DynamicObject->asBaseDir);
bool parse = false;
int cabindex = 0;
DynamicObject->mdKabina = NULL; // likwidacja wskaźnika na dotychczasową kabinę
@@ -6469,9 +6474,32 @@ bool TTrain::InitializeCab(int NewCabNo, std::string const &asFileName)
// matched the token, grab the next one
continue;
}
// TODO: add "pydestination:"
else if (token == "pyscreen:")
{
pyScreens.init(parser, DynamicObject->mdKabina, DynamicObject->name(), NewCabNo);
std::string submodelname, renderername;
parser.getTokens( 2 );
parser
>> submodelname
>> renderername;
auto const *submodel { DynamicObject->mdKabina->GetFromName( submodelname ) };
if( submodel == nullptr ) {
WriteLog( "Python Screen: submodel " + submodelname + " not found - Ignoring screen" );
continue;
}
auto const material { submodel->GetMaterial() };
if( material <= 0 ) {
// sub model nie posiada tekstury lub tekstura wymienna - nie obslugiwana
WriteLog( "Python Screen: invalid texture id " + std::to_string( material ) + " - Ignoring screen" );
continue;
}
// record renderer and material binding for future update requests
m_screens.emplace_back(
( substr_path(renderername).empty() ? // supply vehicle folder as path if none is provided
DynamicObject->asBaseDir + renderername :
renderername ),
material );
}
// btLampkaUnknown.Init("unknown",mdKabina,false);
} while (token != "");
@@ -6480,7 +6508,6 @@ bool TTrain::InitializeCab(int NewCabNo, std::string const &asFileName)
{
return false;
}
pyScreens.start();
if (DynamicObject->mdKabina)
{
// configure placement of sound emitters which aren't bound with any device model, and weren't placed manually

View File

@@ -628,12 +628,10 @@ private:
float fConverterTimer; // hunter-261211: dla przekaznika
float fMainRelayTimer; // hunter-141211: zalaczanie WSa z opoznieniem
float fCzuwakTestTimer; // hunter-091012: do testu czuwaka
float fLightsTimer; // yB 150617: timer do swiatel
float fScreenTimer { 0.f };
bool CAflag { false }; // hunter-131211: dla osobnego zbijania CA i SHP
double fPoslizgTimer;
TTrack *tor;
// McZapkie-240302 - przyda sie do tachometru
float fTachoVelocity{ 0.0f };
float fTachoVelocityJump{ 0.0f }; // ze skakaniem
@@ -659,10 +657,8 @@ private:
bool bHeat[8]; // grzanie
// McZapkie: do syczenia
float fPPress, fNPress;
// float fSPPress, fSNPress;
int iSekunda; // Ra: sekunda aktualizacji pr?dko?ci
int iRadioChannel { 1 }; // numer aktualnego kana?u radiowego
TPythonScreens pyScreens;
std::vector<std::pair<std::string, material_handle>> m_screens;
public:
float fPress[20][3]; // cisnienia dla wszystkich czlonow

View File

@@ -16,7 +16,6 @@ http://mozilla.org/MPL/2.0/.
#include "globals.h"
#include "simulation.h"
#include "train.h"
#include "pyint.h"
#include "sceneeditor.h"
#include "renderer.h"
#include "uilayer.h"
@@ -129,6 +128,7 @@ eu07_application::init( int Argc, char *Argv[] ) {
if( ( result = init_audio() ) != 0 ) {
return result;
}
m_taskqueue.init();
if( ( result = init_modes() ) != 0 ) {
return result;
}
@@ -140,7 +140,7 @@ int
eu07_application::run() {
// main application loop
while( ( false == glfwWindowShouldClose( m_window ) )
while( ( false == glfwWindowShouldClose( m_windows.front() ) )
&& ( false == m_modestack.empty() )
&& ( true == m_modes[ m_modestack.top() ]->update() )
&& ( true == GfxRenderer.Render() ) ) {
@@ -151,6 +151,12 @@ eu07_application::run() {
return 0;
}
bool
eu07_application::request( python_taskqueue::task_request const &Task ) {
return m_taskqueue.insert( Task );
}
void
eu07_application::exit() {
@@ -159,10 +165,11 @@ eu07_application::exit() {
ui_layer::shutdown();
glfwDestroyWindow( m_window );
for( auto *window : m_windows ) {
glfwDestroyWindow( window );
}
glfwTerminate();
TPythonInterpreter::killInstance();
m_taskqueue.exit();
}
void
@@ -197,7 +204,7 @@ eu07_application::push_mode( eu07_application::mode const Mode ) {
void
eu07_application::set_title( std::string const &Title ) {
glfwSetWindowTitle( m_window, Title.c_str() );
glfwSetWindowTitle( m_windows.front(), Title.c_str() );
}
void
@@ -217,17 +224,13 @@ eu07_application::set_cursor( int const Mode ) {
void
eu07_application::set_cursor_pos( double const Horizontal, double const Vertical ) {
if( m_window != nullptr ) {
glfwSetCursorPos( m_window, Horizontal, Vertical );
}
glfwSetCursorPos( m_windows.front(), Horizontal, Vertical );
}
void
eu07_application::get_cursor_pos( double &Horizontal, double &Vertical ) const {
if( m_window != nullptr ) {
glfwGetCursorPos( m_window, &Horizontal, &Vertical );
}
glfwGetCursorPos( m_windows.front(), &Horizontal, &Vertical );
}
void
@@ -262,6 +265,24 @@ eu07_application::on_scroll( double const Xoffset, double const Yoffset ) {
m_modes[ m_modestack.top() ]->on_scroll( Xoffset, Yoffset );
}
GLFWwindow *
eu07_application::window( int const Windowindex ) {
if( Windowindex >= 0 ) {
return (
Windowindex < m_windows.size() ?
m_windows[ Windowindex ] :
nullptr );
}
// for index -1 create a new child window
glfwWindowHint( GLFW_VISIBLE, GL_FALSE );
auto *childwindow = glfwCreateWindow( 1, 1, "eu07helper", nullptr, m_windows.front() );
if( childwindow != nullptr ) {
m_windows.emplace_back( childwindow );
}
return childwindow;
}
// private:
void
@@ -433,7 +454,7 @@ eu07_application::init_glfw() {
// switch off the topmost flag
::SetWindowPos( Hwnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE );
#endif
m_window = window;
m_windows.emplace_back( window );
return 0;
}
@@ -441,16 +462,17 @@ eu07_application::init_glfw() {
void
eu07_application::init_callbacks() {
glfwSetFramebufferSizeCallback( m_window, window_resize_callback );
glfwSetCursorPosCallback( m_window, cursor_pos_callback );
glfwSetMouseButtonCallback( m_window, mouse_button_callback );
glfwSetKeyCallback( m_window, key_callback );
glfwSetScrollCallback( m_window, scroll_callback );
glfwSetWindowFocusCallback( m_window, focus_callback );
auto *window { m_windows.front() };
glfwSetFramebufferSizeCallback( window, window_resize_callback );
glfwSetCursorPosCallback( window, cursor_pos_callback );
glfwSetMouseButtonCallback( window, mouse_button_callback );
glfwSetKeyCallback( window, key_callback );
glfwSetScrollCallback( window, scroll_callback );
glfwSetWindowFocusCallback( window, focus_callback );
{
int width, height;
glfwGetFramebufferSize( m_window, &width, &height );
window_resize_callback( m_window, width, height );
glfwGetFramebufferSize( window, &width, &height );
window_resize_callback( window, width, height );
}
}
@@ -462,8 +484,8 @@ eu07_application::init_gfx() {
return -1;
}
if( ( false == GfxRenderer.Init( m_window ) )
|| ( false == ui_layer::init( m_window ) ) ) {
if( ( false == GfxRenderer.Init( m_windows.front() ) )
|| ( false == ui_layer::init( m_windows.front() ) ) ) {
return -1;
}

View File

@@ -10,6 +10,7 @@ http://mozilla.org/MPL/2.0/.
#pragma once
#include "applicationmode.h"
#include "pyint.h"
class eu07_application {
@@ -29,6 +30,8 @@ public:
init( int Argc, char *Argv[] );
int
run();
bool
request( python_taskqueue::task_request const &Task );
void
exit();
void
@@ -57,10 +60,9 @@ public:
on_mouse_button( int const Button, int const Action, int const Mods );
void
on_scroll( double const Xoffset, double const Yoffset );
inline
// gives access to specified window, creates a new window if index == -1
GLFWwindow *
window() {
return m_window; }
window( int const Windowindex = 0 );
private:
// types
@@ -77,9 +79,10 @@ private:
int init_audio();
int init_modes();
// members
GLFWwindow * m_window { nullptr };
modeptr_array m_modes { nullptr }; // collection of available application behaviour modes
mode_stack m_modestack; // current behaviour mode
python_taskqueue m_taskqueue;
std::vector<GLFWwindow *> m_windows;
};
extern eu07_application Application;

View File

@@ -103,6 +103,8 @@
#include <glm/gtx/rotate_vector.hpp>
#include <glm/gtx/norm.hpp>
int const null_handle = 0;
#include "openglmatrixstack.h"
#include "openglcolor.h"

View File

@@ -313,4 +313,61 @@ glm::dvec3 LoadPoint( class cParser &Input );
std::string
deserialize_random_set( cParser &Input, char const *Break = "\n\r\t ;" );
namespace threading {
// simple POD pairing of a data item and a mutex
// NOTE: doesn't do any locking itself, it's merely for cleaner argument arrangement and passing
template <typename Type_>
struct lockable {
Type_ data;
std::mutex mutex;
};
// basic wrapper simplifying use of std::condition_variable for most typical cases.
// has its own mutex and secondary variable to ignore spurious wakeups
class condition_variable {
public:
// methods
void
wait() {
std::unique_lock<std::mutex> lock( m_mutex );
m_condition.wait(
lock,
[ this ]() {
return m_spurious == false; } ); }
template< class Rep_, class Period_ >
void
wait_for( const std::chrono::duration<Rep_, Period_> &Time ) {
std::unique_lock<std::mutex> lock( m_mutex );
m_condition.wait_for(
lock,
Time,
[ this ]() {
return m_spurious == false; } ); }
void
notify_one() {
spurious( false );
m_condition.notify_one();
}
void
notify_all() {
spurious( false );
m_condition.notify_all();
}
void
spurious( bool const Spurious ) {
std::lock_guard<std::mutex> lock( m_mutex );
m_spurious = Spurious; }
private:
// members
mutable std::mutex m_mutex;
std::condition_variable m_condition;
bool m_spurious { true };
};
} // threading
//---------------------------------------------------------------------------