16
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-07-20 11:29:18 +02:00

Reorganize source files into logical subdirectories

Co-authored-by: Hirek193 <23196899+Hirek193@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-03-14 19:01:57 +00:00
parent f981f81d55
commit 0531086bb9
221 changed files with 131 additions and 108 deletions

626
scripting/PyInt.cpp Normal file
View File

@@ -0,0 +1,626 @@
/*
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 "dictionary.h"
#include "application.h"
#include "Logs.h"
#include "Globals.h"
#ifdef __GNUC__
#pragma GCC diagnostic ignored "-Wwrite-strings"
#endif
#include <simulation.h>
void render_task::run()
{
// convert provided input to a python dictionary
auto *input = PyDict_New();
if (input == nullptr)
{
cancel();
return;
}
for (auto const &datapair : m_input->floats)
{
auto *value{PyGetFloat(datapair.second)};
PyDict_SetItemString(input, datapair.first.c_str(), value);
Py_DECREF(value);
}
for (auto const &datapair : m_input->integers)
{
auto *value{PyGetInt(datapair.second)};
PyDict_SetItemString(input, datapair.first.c_str(), value);
Py_DECREF(value);
}
for (auto const &datapair : m_input->bools)
{
auto *value{PyGetBool(datapair.second)};
PyDict_SetItemString(input, datapair.first.c_str(), value);
}
for (auto const &datapair : m_input->strings)
{
auto *value{PyGetString(datapair.second.c_str())};
PyDict_SetItemString(input, datapair.first.c_str(), value);
Py_DECREF(value);
}
for (auto const &datapair : m_input->vec2_lists)
{
PyObject *list = PyList_New(datapair.second.size());
for (size_t i = 0; i < datapair.second.size(); i++)
{
auto const &vec = datapair.second[i];
WriteLog("passing " + glm::to_string(vec));
PyObject *tuple = PyTuple_New(2);
PyTuple_SetItem(tuple, 0, PyGetFloat(vec.x)); // steals ref
PyTuple_SetItem(tuple, 1, PyGetFloat(vec.y)); // steals ref
PyList_SetItem(list, i, tuple); // steals ref
}
PyDict_SetItemString(input, datapair.first.c_str(), list);
Py_DECREF(list);
}
m_input = nullptr;
// call the renderer
auto *output{PyObject_CallMethod(m_renderer, const_cast<char *>("render"), const_cast<char *>("O"), input)};
Py_DECREF(input);
if (output != nullptr)
{
auto *outputWidth = PyObject_CallMethod(m_renderer, const_cast<char *>("get_width"), nullptr);
auto *outputHeight = PyObject_CallMethod(m_renderer, const_cast<char *>("get_height"), nullptr);
if (outputWidth != nullptr && outputHeight != nullptr && m_target != nullptr)
{
const int screenWidth = static_cast<int>(PyInt_AsLong(outputWidth));
const int screenHeight = static_cast<int>(PyInt_AsLong(outputHeight));
const bool useRgb = (false && !Global.gfx_usegles);
const int glFormat = useRgb ? GL_SRGB8 : GL_SRGB8_ALPHA8;
const int glComponents = useRgb ? GL_RGB : GL_RGBA;
const size_t bytesPerPixel = useRgb ? 3u : 4u;
const size_t expectedBytes = static_cast<size_t>(screenWidth) * static_cast<size_t>(screenHeight) * bytesPerPixel;
Py_ssize_t pythonBufferBytes = 0;
char *pythonBufferPtr = nullptr;
const bool bufferExtracted =
(PyString_AsStringAndSize(output, &pythonBufferPtr, &pythonBufferBytes) == 0)
&& (pythonBufferPtr != nullptr);
if (!bufferExtracted)
{
ErrorLog("Python screen renderer: output is not a valid byte buffer");
}
else if (pythonBufferBytes < static_cast<Py_ssize_t>(expectedBytes))
{
ErrorLog(std::format("Python screen renderer: output buffer too small ({} bytes, expected {})", pythonBufferBytes, expectedBytes));
}
else
{
std::lock_guard guard(m_target->mutex);
if (m_target->image.size() != expectedBytes)
m_target->image.resize(expectedBytes);
std::memcpy(m_target->image.data(), pythonBufferPtr, expectedBytes);
m_target->width = screenWidth;
m_target->height = screenHeight;
m_target->components = glComponents;
m_target->format = glFormat;
m_target->timestamp = std::chrono::high_resolution_clock::now();
}
}
if (outputHeight != nullptr)
Py_DECREF(outputHeight);
if (outputWidth != nullptr)
Py_DECREF(outputWidth);
Py_DECREF(output);
}
// get commands from renderer
auto *commandsPO = PyObject_CallMethod(m_renderer, const_cast<char *>("getCommands"), nullptr);
if (commandsPO != nullptr)
{
std::vector<std::string> commands = python_external_utils::PyObjectToStringArray(commandsPO);
Py_DECREF(commandsPO);
// we perform any actions ONLY when there are any commands in buffer
if (!commands.empty())
{
for (const auto &cmd : commands)
{
std::string baseCmd;
int p1 = 0, p2 = 0;
size_t pos1 = cmd.find(';');
if (pos1 == std::string::npos)
{
baseCmd = cmd;
}
else
{
baseCmd = cmd.substr(0, pos1);
size_t pos2 = cmd.find(';', pos1 + 1);
if (pos2 == std::string::npos)
{
p1 = std::stoi(cmd.substr(pos1 + 1));
}
else
{
p1 = std::stoi(cmd.substr(pos1 + 1, pos2 - pos1 - 1));
p2 = std::stoi(cmd.substr(pos2 + 1));
}
}
auto it = simulation::commandMap.find(baseCmd);
if (it != simulation::commandMap.end())
{
command_data cd;
cd.command = it->second;
cd.action = GLFW_PRESS;
cd.param1 = p1;
cd.param2 = p2;
WriteLog("Python: Executing command [" + baseCmd + "] with params: P1=" + std::to_string(p1) + " P2=" + std::to_string(p2) +
" Target ID=" + std::to_string(simulation::Train->id()));
simulation::Commands.push(cd, static_cast<size_t>(command_target::vehicle) | simulation::Train->id());
}
else
{
ErrorLog("Python: Command [" + baseCmd + "] not found!");
}
}
}
}
}
void render_task::upload()
{
if (Global.python_uploadmain && m_target && m_target->shared_tex)
{
m_target->shared_tex->update_from_memory(m_target->width, m_target->height, reinterpret_cast<const uint8_t *>(m_target->image.data()));
// glBindTexture(GL_TEXTURE_2D, m_target->shared_tex->get_id());
// glTexImage2D(
// GL_TEXTURE_2D, 0,
// m_target->format,
// m_target->width, m_target->height, 0,
// m_target->components, GL_UNSIGNED_BYTE, m_target->image);
//
// if (Global.python_mipmaps)
//{
// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
// glGenerateMipmap(GL_TEXTURE_2D);
//}
// else
//{
// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
//}
//
// if (Global.python_threadedupload)
// glFlush();
}
}
void render_task::cancel() {}
// initializes the module. returns true on success
auto python_taskqueue::init() -> bool
{
crashreport_add_info("python.threadedupload", Global.python_threadedupload ? "yes" : "no");
crashreport_add_info("python.uploadmain", Global.python_uploadmain ? "yes" : "no");
#ifdef _WIN32
if (sizeof(void *) == 8)
Py_SetPythonHome(const_cast<char *>("python64"));
else
Py_SetPythonHome(const_cast<char *>("python"));
#elif __linux__
if (sizeof(void *) == 8)
Py_SetPythonHome(const_cast<char *>("linuxpython64"));
else
Py_SetPythonHome(const_cast<char *>("linuxpython"));
#elif __APPLE__
if (sizeof(void *) == 8)
Py_SetPythonHome(const_cast<char *>("macpython64"));
else
Py_SetPythonHome(const_cast<char *>("macpython"));
#endif
Py_InitializeEx(0);
PyEval_InitThreads();
PyObject *stringiomodule{nullptr};
PyObject *stringioclassname{nullptr};
PyObject *stringioobject{nullptr};
// 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;
}
stringiomodule = PyImport_ImportModule("cStringIO");
stringioclassname = (stringiomodule != nullptr ? PyObject_GetAttrString(stringiomodule, "StringIO") : nullptr);
stringioobject = (stringioclassname != nullptr ? PyObject_CallObject(stringioclassname, nullptr) : nullptr);
m_stderr = {(stringioobject == nullptr ? nullptr : PySys_SetObject(const_cast<char *>("stderr"), stringioobject) != 0 ? nullptr : stringioobject)};
if (false == run_file("abstractscreenrenderer"))
{
goto release_and_exit;
}
// release the lock, save the state for future use
m_mainthread = PyEval_SaveThread();
WriteLog("Python Interpreter: setup complete");
// init workers
for (auto &worker : m_workers)
{
GLFWwindow *openglcontextwindow = nullptr;
if (Global.python_threadedupload)
openglcontextwindow = Application.window(-1);
worker = std::jthread(&python_taskqueue::run, this, openglcontextwindow, std::ref(m_tasks), std::ref(m_uploadtasks), std::ref(m_condition), std::ref(m_exit));
if (false == worker.joinable())
{
return false;
}
}
m_initialized = true;
return true;
release_and_exit:
PyEval_ReleaseLock();
return false;
}
// shuts down the module
void python_taskqueue::exit()
{
if (!m_initialized)
return;
// 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
m_workers = {};
// 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)
{
task->cancel();
}
// take a bow
acquire_lock();
Py_Finalize();
}
// 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
{
if (!m_initialized || (false == Global.python_enabled) || (Task.renderer.empty()) || (Task.input == nullptr) || (Task.target == 0))
{
return false;
}
auto *renderer{fetch_renderer(Task.renderer)};
if (renderer == nullptr)
{
return false;
}
auto newtask = std::make_shared<render_task>(renderer, Task.input, Task.target);
bool newtaskinserted{false};
// acquire a lock on the task queue and add the new task
{
std::lock_guard<std::mutex> lock(m_tasks.mutex);
// check the task list for a pending request with the same target
for (auto &task : m_tasks.data)
{
if (task->target() == Task.target)
{
// replace pending task in the slot with the more recent one
task->cancel();
task = newtask;
newtaskinserted = true;
break;
}
}
if (false == newtaskinserted)
{
m_tasks.data.emplace_back(newtask);
}
}
// potentially wake a worker to handle the new task
m_condition.notify_one();
// all done
return true;
}
// 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;
}
// acquires the python gil and sets the main thread as current
void python_taskqueue::acquire_lock()
{
PyEval_RestoreThread(m_mainthread);
}
// releases the python gil and swaps the main thread out
void python_taskqueue::release_lock()
{
PyEval_SaveThread();
}
auto python_taskqueue::fetch_renderer(std::string const Renderer) -> PyObject *
{
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};
PyObject *rendererarguments{nullptr};
PyObject *renderername{nullptr};
acquire_lock();
{
if (m_main == nullptr)
{
ErrorLog("Python Renderer: __main__ module is missing");
goto cache_and_return;
}
if (false == run_file(file, path))
{
goto cache_and_return;
}
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);
PyObject_CallMethod(renderer, const_cast<char *>("manul_set_format"), const_cast<char *>("(s)"), "RGBA");
if (PyErr_Occurred() != nullptr)
{
error();
renderer = nullptr;
}
cache_and_return:
// clean up after yourself
if (rendererarguments != nullptr)
{
Py_DECREF(rendererarguments);
}
}
release_lock();
// cache the failures as well so we don't try again on subsequent requests
m_renderers.emplace(Renderer, renderer);
return renderer;
}
void python_taskqueue::run(GLFWwindow *Context, rendertask_sequence &Tasks, uploadtask_sequence &Upload_Tasks, threading::condition_variable &Condition, std::atomic<bool> &Exit)
{
if (Context)
glfwMakeContextCurrent(Context);
// create a state object for this thread
PyEval_AcquireLock();
auto *threadstate{PyThreadState_New(m_mainthread->interp)};
PyEval_ReleaseLock();
std::shared_ptr<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_RestoreThread(threadstate);
{
// execute python code
task->run();
if (Context)
task->upload();
else
{
std::lock_guard<std::mutex> lock(Upload_Tasks.mutex);
Upload_Tasks.data.push_back(task);
}
if (PyErr_Occurred() != nullptr)
error();
}
// clear the thread state
PyEval_SaveThread();
}
// 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::update()
{
std::lock_guard<std::mutex> lock(m_uploadtasks.mutex);
for (auto &task : m_uploadtasks.data)
task->upload();
m_uploadtasks.data.clear();
}
void python_taskqueue::error()
{
if (m_stderr != nullptr)
{
// std err pythona jest buforowane
PyErr_Print();
auto *errortext{PyObject_CallMethod(m_stderr, const_cast<char *>("getvalue"), nullptr)};
ErrorLog(PyString_AsString(errortext));
// czyscimy bufor na kolejne bledy
PyObject_CallMethod(m_stderr, const_cast<char *>("truncate"), const_cast<char *>("i"), 0);
}
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");
}
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)
{
ErrorLog(PyString_AsString(tracebacktext));
}
else
{
WriteLog("Python Interpreter: failed to retrieve the stack traceback");
}
}
}
std::vector<std::string> python_external_utils::PyObjectToStringArray(PyObject *pyList)
{
std::vector<std::string> result;
std::vector<std::string> emptyIfError = {};
if (!PySequence_Check(pyList))
{
ErrorLog("Python: Failed to convert PyObject -> vector<string>");
return emptyIfError;
}
Py_ssize_t size = PySequence_Size(pyList);
for (Py_ssize_t i = 0; i < size; ++i)
{
PyObject *item = PySequence_GetItem(pyList, i); // Increments reference count
if (item == nullptr)
{
ErrorLog("Python: Failed to get item from sequence.");
return emptyIfError;
}
const char *str = PyString_AsString(item);
if (str == nullptr)
{
Py_DECREF(item);
ErrorLog("Python: Failed to convert item to string.");
return emptyIfError;
}
result.push_back(std::string(str));
Py_DECREF(item); // Decrease reference count for the item
}
return result;
}
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif

159
scripting/PyInt.h Normal file
View File

@@ -0,0 +1,159 @@
/*
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/.
*/
#ifndef PYINT_H
#define PYINT_H
#ifdef _POSIX_C_SOURCE
#undef _POSIX_C_SOURCE
#endif
#ifdef _XOPEN_SOURCE
#undef _XOPEN_SOURCE
#endif
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 5033)
#endif
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wregister"
#endif
#ifdef WITH_PYTHON
#ifdef _DEBUG
#undef _DEBUG // bez tego macra Py_DECREF powoduja problemy przy linkowaniu
#include "Python.h"
#define _DEBUG
#else
#include "Python.h"
#endif
#else
#define PyObject void
#define PyThreadState void
#endif
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif
#ifdef _MSC_VER
#pragma warning(pop)
#endif
#include "Classes.h"
#include "utilities.h"
#include "Texture.h"
#include <thread>
#define PyGetFloat(param) PyFloat_FromDouble(param)
#define PyGetInt(param) PyInt_FromLong(param)
#define PyGetBool(param) param ? Py_True : Py_False
#define PyGetString(param) PyString_FromString(param)
// python rendertarget
struct python_rt
{
std::mutex mutex;
ITexture *shared_tex;
int format;
int components;
int width;
int height;
std::string image;
std::chrono::high_resolution_clock::time_point timestamp;
};
// TODO: extract common base and inherit specialization from it
class render_task
{
public:
// constructors
render_task(PyObject *Renderer, std::shared_ptr<dictionary_source> Input, std::shared_ptr<python_rt> Target) : m_renderer(Renderer), m_input(Input), m_target(Target) {}
// methods
void run();
void upload();
void cancel();
auto target() const -> std::shared_ptr<python_rt>
{
return m_target;
}
private:
// members
PyObject *m_renderer{nullptr};
std::shared_ptr<dictionary_source> m_input{nullptr};
std::shared_ptr<python_rt> m_target{nullptr};
};
class python_taskqueue
{
public:
// types
struct task_request
{
std::string const &renderer;
std::shared_ptr<dictionary_source> input;
std::shared_ptr<python_rt> 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;
// acquires the python gil and sets the main thread as current
void acquire_lock();
// releases the python gil and swaps the main thread out
void release_lock();
void update();
private:
// types
static int const WORKERCOUNT{1};
using worker_array = std::array<std::jthread, WORKERCOUNT>;
using rendertask_sequence = threading::lockable<std::deque<std::shared_ptr<render_task>>>;
using uploadtask_sequence = threading::lockable<std::deque<std::shared_ptr<render_task>>>;
// methods
auto fetch_renderer(std::string const Renderer) -> PyObject *;
void run(GLFWwindow *Context, rendertask_sequence &Tasks, uploadtask_sequence &Upload_Tasks, threading::condition_variable &Condition, std::atomic<bool> &Exit);
void error();
// members
PyObject *m_main{nullptr};
PyObject *m_stderr{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;
uploadtask_sequence m_uploadtasks;
bool m_initialized{false};
};
class python_external_utils
{
public:
static std::vector<std::string> PyObjectToStringArray(PyObject *pyList);
};
#endif

42
scripting/PyIntStub.cpp Normal file
View File

@@ -0,0 +1,42 @@
/*
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"
bool python_taskqueue::init()
{
return false;
}
void python_taskqueue::exit()
{
}
bool python_taskqueue::insert(python_taskqueue::task_request const &Task)
{
return false;
}
bool python_taskqueue::run_file(std::string const &File, std::string const &Path)
{
return false;
}
void python_taskqueue::acquire_lock()
{
}
void python_taskqueue::release_lock()
{
}
void python_taskqueue::update()
{
}

400
scripting/ladderlogic.cpp Normal file
View File

@@ -0,0 +1,400 @@
/*
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 "ladderlogic.h"
#include "parser.h"
#include "utilities.h"
#include "Logs.h"
namespace plc {
int basic_element::blank = -1;
auto
basic_element::input() -> int & {
switch( (basic_element::type_e)data.index() ) {
case basic_element::type_e::variable: {
return std::get<variable>(data).value;
}
case basic_element::type_e::timer: {
return std::get<timer>(data).value;
}
case basic_element::type_e::counter: {
return std::get<counter>(data).value;
}
}
return blank; // not reachable
}
auto
basic_element::output() const -> int {
switch( (basic_element::type_e)data.index() ) {
case basic_element::type_e::variable: {
return std::get<variable>(data).value;
}
case basic_element::type_e::timer: {
return ( std::get<timer>(data).time_elapsed >= std::get<timer>(data).time_preset ? std::get<timer>(data).value : 0 );
}
case basic_element::type_e::counter: {
return ( std::get<counter>(data).count_value >= std::get<counter>(data).count_limit ? 1 : 0 );
}
}
return -1; // not reachable
}
auto
basic_controller::input( element_handle const Element ) -> int & {
return m_elements[ Element - 1 ].input();
}
auto
basic_controller::output( element_handle const Element ) const -> int {
return m_elements[ Element - 1 ].output();
}
auto
basic_controller::load( std::string const &Filename ) -> bool {
m_program.clear();
m_updateaccumulator = 0.0;
m_programfilename = Filename;
cParser input( m_programfilename, cParser::buffer_FILE );
bool result { false };
while( true == deserialize_operation( input ) ) {
result = true; // once would suffice but, eh
}
return result;
}
auto
basic_controller::update( double const Timestep ) -> int {
if( false == m_timerhandles.empty() ) {
// update timers
m_updateaccumulator += Timestep;
auto const updatecount = std::floor( m_updateaccumulator / m_updaterate );
if( updatecount > 0 ) {
auto const updateamount = static_cast<short>( updatecount * 1000 * m_updaterate );
for( auto const timerhandle : m_timerhandles ) {
auto &timer{ element( timerhandle ) };
auto &timerdata{ std::get<basic_element::timer>(timer.data) };
if( timer.input() > 0 ) {
timerdata.time_elapsed =
std::min<short>(
timerdata.time_preset,
timerdata.time_elapsed + updateamount );
}
else {
timerdata.time_elapsed = 0;
}
}
m_updateaccumulator -= m_updaterate * updatecount;
}
}
return run();
}
std::map<std::string, basic_controller::opcode_e> const basic_controller::m_operationcodemap = {
{ "ld", opcode_e::op_ld }, { "ldi", opcode_e::op_ldi },
{ "and", opcode_e::op_and }, { "ani", opcode_e::op_ani }, { "anb", opcode_e::op_anb },
{ "or", opcode_e::op_or }, { "ori", opcode_e::op_ori }, { "orb", opcode_e::op_orb },
{ "out", opcode_e::op_out }, { "set", opcode_e::op_set }, { "rst", opcode_e::op_rst },
{ "end", opcode_e::op_nop }
};
auto
basic_controller::deserialize_operation( cParser &Input ) -> bool {
auto operationdata{ Input.getToken<std::string>( true, "\n\r" ) };
if( true == operationdata.empty() ) { return false; }
operation operation = { opcode_e::op_nop, 0, 0, 0 };
cParser operationparser( operationdata, cParser::buffer_TEXT );
// HACK: operation potentially contains 1-2 parameters so we try to grab the whole set
operationparser.getTokens( 3, "\t " );
std::string
operationname,
operationelement,
operationparameter;
operationparser
>> operationname
>> operationelement
>> operationparameter;
auto const lookup { m_operationcodemap.find( operationname ) };
operation.code = (
lookup != m_operationcodemap.end() ?
lookup->second :
opcode_e::op_nop );
if( lookup == m_operationcodemap.end() ) {
log_error( "contains unknown command \"" + operationname + "\"", Input.Line() - 1 );
}
if( operation.code == opcode_e::op_nop ) { return true; }
if( false == operationelement.empty() ) {
operation.element =
find_or_insert(
operationelement,
guess_element_type_from_name( operationelement ) );
}
if( false == operationparameter.empty() ) {
auto const parameter{ split_string_and_number( operationparameter ) };
operation.parameter1 = static_cast<short>( parameter.second );
}
m_program.emplace_back( operation );
return true;
}
auto
basic_controller::insert( std::string const Name, basic_element Element ) -> element_handle {
m_elements.push_back( Element );
m_elementnames.push_back( Name );
auto const elementhandle{ static_cast<short>( m_elements.size() ) };
// for timers make note of the element in the timer list
if( (basic_element::type_e)Element.data.index() == basic_element::type_e::timer ) {
m_timerhandles.push_back( elementhandle );
}
return elementhandle;
}
// runs one cycle of current program
auto
basic_controller::run() -> int {
m_accumulator.clear();
m_popstack = false;
auto programline { 1 };
for( auto const &operation : m_program ) {
// TBD: replace switch with function table for better readability/maintenance?
switch( operation.code ) {
case opcode_e::op_ld: {
if( m_popstack ) {
if( false == m_accumulator.empty() ) {
m_accumulator.pop_back();
}
m_popstack = false;
}
m_accumulator.emplace_back( output( operation.element ) );
break;
}
case opcode_e::op_ldi: {
if( m_popstack ) {
if( false == m_accumulator.empty() ) {
m_accumulator.pop_back();
}
m_popstack = false;
}
m_accumulator.emplace_back( inverse( output( operation.element ) ) );
break;
}
case opcode_e::op_and: {
if( m_accumulator.empty() ) {
log_error( "attempted AND with empty accumulator", programline );
break;
}
m_accumulator.back() &= output( operation.element );
break;
}
case opcode_e::op_ani: {
if( m_accumulator.empty() ) {
log_error( "attempted ANI with empty accumulator", programline );
break;
}
m_accumulator.back() &= inverse( output( operation.element ) );
break;
}
case opcode_e::op_anb: {
if( m_accumulator.size() < 2 ) {
log_error( "attempted ANB with empty stack", programline );
break;
}
auto const operand { m_accumulator.back() };
m_accumulator.pop_back();
m_accumulator.back() &= operand;
break;
}
case opcode_e::op_or: {
if( m_accumulator.empty() ) {
log_error( "attempted OR with empty accumulator", programline );
break;
}
m_accumulator.back() |= output( operation.element );
break;
}
case opcode_e::op_ori : {
if( m_accumulator.empty() ) {
log_error( "attempted ORI with empty accumulator", programline );
break;
}
m_accumulator.back() |= inverse( output( operation.element ) );
break;
}
case opcode_e::op_orb: {
if( m_accumulator.size() < 2 ) {
log_error( "attempted ORB with empty stack", programline );
break;
}
auto const operand{ m_accumulator.back() };
m_accumulator.pop_back();
m_accumulator.back() |= operand;
break;
}
case opcode_e::op_out: {
if( m_accumulator.empty() ) {
log_error( "attempted OUT with empty accumulator", programline );
break;
}
auto &target { element( operation.element ) };
auto const initialstate { target.input() };
target.input() = m_accumulator.back();
// additional operations for advanced element types
switch( (basic_element::type_e)target.data.index() ) {
case basic_element::type_e::timer: {
std::get<basic_element::timer>(target.data).time_preset = operation.parameter1;
break;
}
case basic_element::type_e::counter: {
std::get<basic_element::counter>(target.data).count_limit = operation.parameter1;
// increase counter value on input activation
if( ( initialstate == 0 ) && ( target.input() != 0 ) ) {
/*
// TBD: use overflow-prone version instead of safe one?
target.data.counter.count_value += 1;
*/
std::get<basic_element::counter>(target.data).count_value =
std::min<short>(
std::get<basic_element::counter>(target.data).count_limit,
std::get<basic_element::counter>(target.data).count_value + 1 );
}
break;
}
}
// accumulator was published at least once, next ld(i) operation will start a new rung
m_popstack = true;
break;
}
case opcode_e::op_set: {
if( m_accumulator.empty() ) {
log_error( "attempted SET with empty accumulator", programline );
break;
}
if( m_accumulator.back() == 0 ) {
break;
}
auto &target { element( operation.element ) };
auto const initialstate { target.input() };
target.input() = m_accumulator.back();
// additional operations for advanced element types
switch( (basic_element::type_e)target.data.index() ) {
case basic_element::type_e::counter: {
// NOTE: siemens counter behavior
// TODO: check whether this is true for mitsubishi
std::get<basic_element::counter>(target.data).count_limit = std::get<basic_element::counter>(target.data).count_value;
/*
if( ( initialstate == 0 ) && ( target.input() != 0 ) ) {
target.data.counter.count_value =
std::min<short>(
target.data.counter.count_limit,
target.data.counter.count_value + 1 );
}
*/
break;
}
}
// accumulator was published at least once, next ld(i) operation will start a new rung
m_popstack = true;
break;
}
case opcode_e::op_rst: {
if( m_accumulator.empty() ) {
log_error( "attempted RST with empty accumulator", programline );
break;
}
if( m_accumulator.back() == 0 ) {
break;
}
auto &target{ element( operation.element ) };
target.input() = 0;
// additional operations for advanced element types
switch( (basic_element::type_e)target.data.index() ) {
case basic_element::type_e::counter: {
std::get<basic_element::counter>(target.data).count_value = 0;
break;
}
}
// accumulator was published at least once, next ld(i) operation will start a new rung
m_popstack = true;
break;
}
}
++programline;
}
return 0;
}
void
basic_controller::log_error( std::string const &Error, int const Line ) const {
ErrorLog(
"Bad plc program: \"" + m_programfilename + "\" "
+ Error
+ ( Line > 0 ?
" (line " + to_string( Line ) + ")" :
"" ) );
}
auto
basic_controller::guess_element_type_from_name( std::string const &Name ) const -> basic_element::type_e {
auto const name { split_string_and_number( Name ) };
if( ( name.first == "t" ) || ( name.first == "ton" ) || ( name.first.find( "timer." ) == 0 ) ) {
return basic_element::type_e::timer;
}
if( ( name.first == "c" ) || ( name.first.find( "counter." ) == 0 ) ) {
return basic_element::type_e::counter;
}
return basic_element::type_e::variable;
}
} // plc

169
scripting/ladderlogic.h Normal file
View File

@@ -0,0 +1,169 @@
/*
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/.
*/
#pragma once
#include "Classes.h"
namespace plc {
using element_handle = short;
// basic logic element.
class basic_element {
public:
// types
// rtti
enum class type_e {
variable,
timer,
counter,
};
// constructors
template<typename ...Args_>
basic_element( basic_element::type_e Type = basic_element::type_e::variable, Args_ ...Args );
// methods
// data access
auto input() -> int &;
auto output() const -> int;
private:
// types
// cell content variants
struct variable {
std::int32_t value;
};
struct timer {
std::int32_t value;
short time_preset;
short time_elapsed;
};
struct counter {
std::int32_t value;
short count_limit;
short count_value;
};
// members
std::variant<variable, timer, counter> data;
static int blank;
// friends
friend class basic_controller;
};
class basic_controller {
public:
// methods
auto load( std::string const &Filename ) -> bool;
auto update( double const Timestep ) -> int;
// finds element with specified name, potentially creating new element of specified type initialized with provided arguments. returns: handle to the element
template<typename ...Args_>
auto find_or_insert( std::string const &Name, basic_element::type_e Type = basic_element::type_e::variable, Args_ ...Args ) -> element_handle;
// data access
auto input( element_handle const Element ) -> int &;
auto output( element_handle const Element ) const -> int;
private:
//types
// plc program instruction
enum class opcode_e : short {
op_nop,
op_ld,
op_ldi,
op_and,
op_ani,
op_anb,
op_or,
op_ori,
op_orb,
op_out,
op_set,
op_rst,
};
struct operation {
opcode_e code;
short element;
short parameter1;
short parameter2;
};
// containers
using element_sequence = std::vector<basic_element>;
using name_sequence = std::vector<std::string>;
using operation_sequence = std::vector<operation>;
using handle_sequence = std::vector<element_handle>;
// methods
auto deserialize_operation( cParser &Input ) -> bool;
// adds provided item to the collection. returns: true if there's no duplicate with the same name, false otherwise
auto insert( std::string const Name, basic_element Element ) -> element_handle;
// runs one cycle of current program. returns: error code or 0 if there's no error
auto run() -> int;
void log_error( std::string const &Error, int const Line = -1 ) const;
auto guess_element_type_from_name( std::string const &Name ) const->basic_element::type_e;
inline
auto inverse( int const Value ) const -> int {
return ( Value == 0 ? 1 : 0 ); }
// element access
inline
auto element( element_handle const Element ) const -> basic_element const {
return m_elements[ Element - 1 ]; }
inline
auto element( element_handle const Element ) -> basic_element & {
return m_elements[ Element - 1 ]; }
// members
static std::map<std::string, basic_controller::opcode_e> const m_operationcodemap;
element_sequence m_elements; // collection of elements accessed by the plc program
name_sequence m_elementnames;
handle_sequence m_timerhandles; // indices of timer elements, timer update optimization helper
std::string m_programfilename; // cached filename of currently loaded program
operation_sequence m_program; // current program for the plc
std::vector<int> m_accumulator; // state accumulator for currently processed program rung
bool m_popstack { false }; // whether ld(i) operation should pop the accumulator stack or just add onto it
double m_updateaccumulator { 0.0 }; //
double m_updaterate { 0.1 };
};
template<typename ...Args_>
basic_element::basic_element( basic_element::type_e Type, Args_ ...Args )
{
switch( Type ) {
case type_e::variable: {
data = variable{ Args ... };
break;
}
case type_e::timer: {
data = timer{ Args ... };
break;
}
case type_e::counter: {
data = counter{ Args ... };
break;
}
default: {
// TBD: log error if we get here?
break;
}
}
}
template<typename ...Args_>
auto basic_controller::find_or_insert( std::string const &Name, basic_element::type_e Type, Args_ ...Args ) -> element_handle {
// NOTE: because we expect all lookups to be performed only (once) during controller (code) initialization
// we're using simple linear container for names, to allow for easy access to both elements and their names with the same handle
auto index { 1 };
for( auto const &name : m_elementnames ) {
if( name == Name ) {
return index;
}
++index;
}
// create and insert a new element if we didn't find existing one
return insert( Name, basic_element( Type, Args ... ) );
}
} // plc

212
scripting/lua.cpp Normal file
View File

@@ -0,0 +1,212 @@
#include "stdafx.h"
#include "lua.h"
#include "Event.h"
#include "Logs.h"
#include "MemCell.h"
#include "Driver.h"
#include "lua_ffi.h"
#include "simulation.h"
lua::lua()
{
state = luaL_newstate();
if (!state)
throw std::runtime_error("cannot create lua state");
lua_atpanic(state, atpanic);
luaL_openlibs(state);
lua_getglobal(state, "package");
lua_pushstring(state, "preload");
lua_gettable(state, -2);
lua_pushcclosure(state, openffi, 0);
lua_setfield(state, -2, "eu07.events");
lua_settop(state, 0);
}
lua::~lua()
{
lua_close(state);
state = nullptr;
}
std::string lua::get_error()
{
return std::string(lua_tostring(state, -1));
}
void lua::interpret(std::string file)
{
if (luaL_dofile(state, file.c_str())) {
const char *str = lua_tostring(state, -1);
ErrorLog(std::string(str), logtype::lua);
}
}
// NOTE: we cannot throw exceptions in callbacks
// because it is not supported by LuaJIT on x86 windows
int lua::atpanic(lua_State *s)
{
std::string err(lua_tostring(s, -1));
ErrorLog(err, logtype::lua);
return 0;
}
int lua::openffi(lua_State *s)
{
if (luaL_dostring(s, lua_ffi))
{
ErrorLog(std::string(lua_tostring(s, -1)), logtype::lua);
return 0;
}
return 1;
}
#if defined _WIN32
# if defined __GNUC__
# define EXPORT __attribute__ ((dllexport))
# else
# define EXPORT __declspec(dllexport)
# endif
#elif defined __GNUC__
# define EXPORT __attribute__ ((visibility ("default")))
#else
# define EXPORT
#endif
extern "C"
{
EXPORT basic_event* scriptapi_event_create(const char* name, double delay, double randomdelay, lua::eventhandler_t handler)
{
basic_event *event = new lua_event(handler);
event->m_name = std::string(name);
event->m_delay = delay;
event->m_delayrandom = randomdelay;
if (simulation::Events.insert(event))
return event;
else
return nullptr;
}
EXPORT basic_event* scriptapi_event_find(const char* name)
{
std::string str(name);
basic_event *e = simulation::Events.FindEvent(str);
if (e)
return e;
else
WriteLog("lua: missing event: " + str);
return nullptr;
}
EXPORT TTrack* scriptapi_track_find(const char* name)
{
std::string str(name);
TTrack *track = simulation::Paths.find(str);
if (track)
return track;
else
WriteLog("lua: missing track: " + str);
return nullptr;
}
EXPORT bool scriptapi_track_isoccupied(TTrack* track)
{
if (track)
return !track->IsEmpty();
return false;
}
EXPORT TIsolated* scriptapi_isolated_find(const char* name)
{
std::string str(name);
TIsolated *isolated = TIsolated::Find(name);
if (isolated)
return isolated;
else
WriteLog("lua: missing isolated: " + str);
return nullptr;
}
EXPORT bool scriptapi_isolated_isoccupied(TIsolated* isolated)
{
if (isolated)
return isolated->Busy();
return false;
}
EXPORT const char* scriptapi_event_getname(basic_event *e)
{
if (e)
return e->m_name.c_str();
return nullptr;
}
EXPORT const char* scriptapi_train_getname(TDynamicObject *dyn)
{
if (dyn && dyn->Mechanik)
return dyn->Mechanik->TrainName().c_str();
return nullptr;
}
EXPORT void scriptapi_event_dispatch(basic_event *e, TDynamicObject *activator, double delay)
{
if (e)
simulation::Events.AddToQuery(e, activator, delay);
}
EXPORT double scriptapi_random(double a, double b)
{
return Random(a, b);
}
EXPORT void scriptapi_writelog(const char* txt)
{
WriteLog("lua: log: " + std::string(txt), logtype::lua);
}
EXPORT void scriptapi_writeerrorlog(const char* txt)
{
ErrorLog("lua: log: " + std::string(txt), logtype::lua);
}
struct memcell_values { const char *str; double num1; double num2; };
EXPORT TMemCell* scriptapi_memcell_find(const char *name)
{
std::string str(name);
TMemCell *mc = simulation::Memory.find(str);
if (mc)
return mc;
else
WriteLog("lua: missing memcell: " + str);
return nullptr;
}
EXPORT memcell_values scriptapi_memcell_read(TMemCell *mc)
{
if (!mc)
return { nullptr, 0.0, 0.0 };
return { mc->Text().c_str(), mc->Value1(), mc->Value2() };
}
EXPORT void scriptapi_memcell_update(TMemCell *mc, const char *str, double num1, double num2)
{
if (!mc)
return;
mc->UpdateValues(std::string(str), num1, num2,
basic_event::flags::text | basic_event::flags::value1 | basic_event::flags::value2);
}
EXPORT void scriptapi_dynobj_putvalues(TDynamicObject *dyn, const char *str, double num1, double num2)
{
if (!dyn)
return;
TLocation loc;
if (dyn->Mechanik)
dyn->Mechanik->PutCommand(std::string(str), num1, num2, loc);
else
dyn->MoverParameters->PutCommand(std::string(str), num1, num2, loc);
}
}

22
scripting/lua.h Normal file
View File

@@ -0,0 +1,22 @@
#pragma once
#include <lua.hpp>
class basic_event;
class TDynamicObject;
class lua
{
lua_State *state;
static int atpanic(lua_State *s);
static int openffi(lua_State *s);
public:
lua();
~lua();
std::string get_error();
void interpret(std::string file);
typedef void (*eventhandler_t)(basic_event*, const TDynamicObject*);
};

96
scripting/lua_ffi.h Normal file
View File

@@ -0,0 +1,96 @@
const char lua_ffi[] = R"STRING(
local ffi = require("ffi")
ffi.cdef[[
struct memcell_values { const char *str; double num1; double num2; };
typedef struct TEvent TEvent;
typedef struct TTrack TTrack;
typedef struct TIsolated TIsolated;
typedef struct TDynamicObject TDynamicObject;
typedef struct TMemCell TMemCell;
typedef struct memcell_values memcell_values;
TEvent* scriptapi_event_create(const char* name, double delay, double randomdelay, void (*handler)(TEvent*, TDynamicObject*));
TEvent* scriptapi_event_find(const char* name);
const char* scriptapi_event_getname(TEvent *e);
void scriptapi_event_dispatch(TEvent *e, TDynamicObject *activator, double delay);
TTrack* scriptapi_track_find(const char* name);
bool scriptapi_track_isoccupied(TTrack *track);
TIsolated* scriptapi_isolated_find(const char* name);
bool scriptapi_isolated_isoccupied(TIsolated *isolated);
const char* scriptapi_train_getname(TDynamicObject *dyn);
void scriptapi_dynobj_putvalues(TDynamicObject *dyn, const char *str, double num1, double num2);
TMemCell* scriptapi_memcell_find(const char *name);
memcell_values scriptapi_memcell_read(TMemCell *mc);
void scriptapi_memcell_update(TMemCell *mc, const char *str, double num1, double num2);
double scriptapi_random(double a, double b);
void scriptapi_writelog(const char* txt);
void scriptapi_writeerrorlog(const char* txt);
]]
local ns = ffi.C
local module = {}
module.event_create = ns.scriptapi_event_create
function module.event_preparefunc(f)
return ffi.cast("void (*)(TEvent*, TDynamicObject*)", f)
end
module.event_find = ns.scriptapi_event_find
function module.event_getname(a)
return ffi.string(ns.scriptapi_event_getname(a))
end
module.event_dispatch = ns.scriptapi_event_dispatch
function module.event_dispatch_n(a, b, c)
ns.scriptapi_event_dispatch(ns.scriptapi_event_find(a), b, c)
end
module.track_find = ns.scriptapi_track_find
module.track_isoccupied = ns.scriptapi_track_isoccupied
function module.track_isoccupied_n(a)
return ns.scriptapi_track_isoccupied(ns.scriptapi_track_find(a))
end
module.isolated_find = ns.scriptapi_isolated_find
module.isolated_isoccupied = ns.scriptapi_isolated_isoccupied
function module.isolated_isoccupied_n(a)
return ns.scriptapi_isolated_isoccupied(ns.scriptapi_isolated_find(a))
end
function module.train_getname(a)
return ffi.string(ns.scriptapi_train_getname(a))
end
function module.dynobj_putvalues(a, b)
ns.scriptapi_dynobj_putvalues(a, b.str, b.num1, b.num2)
end
module.memcell_find = ns.scriptapi_memcell_find
function module.memcell_read(a)
native = ns.scriptapi_memcell_read(a)
mc = { }
mc.str = ffi.string(native.str)
mc.num1 = native.num1
mc.num2 = native.num2
return mc
end
function module.memcell_read_n(a)
return module.memcell_read(ns.scriptapi_memcell_find(a))
end
function module.memcell_update(a, b)
ns.scriptapi_memcell_update(a, b.str, b.num1, b.num2)
end
function module.memcell_update_n(a, b)
ns.scriptapi_memcell_update(ns.scriptapi_memcell_find(a), b.str, b.num1, b.num2)
end
module.random = ns.scriptapi_random
module.writelog = ns.scriptapi_writelog
module.writeerrorlog = ns.scriptapi_writeerrorlog
return module;
)STRING";

View File

@@ -0,0 +1,258 @@
#include "stdafx.h"
#include "pythonscreenviewer.h"
#include "application.h"
#include "gl/shader.h"
#include "gl/vao.h"
#include "Logs.h"
void texture_window_resize(GLFWwindow *win, int w, int h)
{
python_screen_viewer *texwindow = (python_screen_viewer*)glfwGetWindowUserPointer(win);
texwindow->notify_window_size(win, w, h);
}
void texture_window_fb_resize(GLFWwindow *win, int w, int h)
{
python_screen_viewer *texwindow = (python_screen_viewer*)glfwGetWindowUserPointer(win);
texwindow->notify_window_fb_size(win, w, h);
}
void texture_window_mouse_button(GLFWwindow *win, int button, int action, int mods)
{
python_screen_viewer *texwindow = (python_screen_viewer*)glfwGetWindowUserPointer(win);
texwindow->notify_click(win, button, action);
}
void texture_window_cursor_pos(GLFWwindow *win, double x, double y)
{
python_screen_viewer *texwindow = (python_screen_viewer*)glfwGetWindowUserPointer(win);
texwindow->notify_cursor_pos(win, x, y);
}
python_screen_viewer::python_screen_viewer(std::shared_ptr<python_rt> rt, std::shared_ptr<std::vector<glm::vec2>> touchlist, std::string surfacename)
{
m_rt = rt;
m_touchlist = touchlist;
for (const auto &viewport : Global.python_viewports) {
if (viewport.surface == surfacename) {
auto conf = std::make_unique<window_state>();
conf->window_size = viewport.size;
conf->offset = viewport.offset;
conf->scale = viewport.scale;
GLFWmonitor *monitor = Application.find_monitor(viewport.monitor);
if (!monitor && viewport.monitor != "window")
continue;
conf->window = Application.window(-1, true, conf->window_size.x, conf->window_size.y,
monitor, false, Global.python_sharectx);
{
int w, h;
glfwGetWindowSize(conf->window, &w, &h);
conf->window_size = glm::ivec2(w, h);
glfwGetFramebufferSize(conf->window, &w, &h);
conf->fb_size = glm::ivec2(w, h);
}
glfwSetWindowUserPointer(conf->window, this);
glfwSetWindowSizeCallback(conf->window, texture_window_resize);
glfwSetFramebufferSizeCallback(conf->window, texture_window_fb_resize);
glfwSetMouseButtonCallback(conf->window, texture_window_mouse_button);
glfwSetCursorPosCallback(conf->window, texture_window_cursor_pos);
m_windows.push_back(std::move(conf));
}
}
if (!m_windows.empty())
m_renderthread = std::make_unique<std::thread>(&python_screen_viewer::threadfunc, this);
}
python_screen_viewer::~python_screen_viewer()
{
m_exit = true;
if (m_renderthread)
m_renderthread->join();
}
void python_screen_viewer::threadfunc()
{
for (auto &window : m_windows) {
glfwMakeContextCurrent(window->window);
glfwSwapInterval(Global.python_vsync ? 1 : 0);
GLuint v;
glGenVertexArrays(1, &v);
glBindVertexArray(v);
glActiveTexture(GL_TEXTURE0);
if (Global.python_sharectx) {
glBindTexture(GL_TEXTURE_2D, m_rt->shared_tex->get_id());
}
else {
GLuint tex;
glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_2D, tex);
}
if (!Global.gfx_usegles && !Global.gfx_shadergamma)
glEnable(GL_FRAMEBUFFER_SRGB);
gl::program::unbind();
gl::buffer::unbind();
window->ubo = std::make_unique<gl::ubo>(sizeof(gl::scene_ubs), 0, GL_STREAM_DRAW);
gl::shader vert("texturewindow.vert");
gl::shader frag("texturewindow.frag");
window->shader = std::make_unique<gl::program>(std::vector<std::reference_wrapper<const gl::shader>>({vert, frag}));
}
while (!m_exit)
{
auto start_time = std::chrono::high_resolution_clock::now();
for (auto &window : m_windows) {
unsigned char *image = nullptr;
int format, components, width, height;
if (!Global.python_sharectx) {
std::lock_guard<std::mutex> guard(m_rt->mutex);
if (window->timestamp == m_rt->timestamp)
continue;
window->timestamp = m_rt->timestamp;
if (m_rt->image.empty())
continue;
format = m_rt->format;
components = m_rt->components;
width = m_rt->width;
height = m_rt->height;
size_t size = width * height * (components == GL_RGB ? 3 : 4);
image = new unsigned char[size];
memcpy(image, m_rt->image.data(), size);
}
glfwMakeContextCurrent(window->window);
gl::program::unbind();
gl::buffer::unbind();
window->shader->bind();
window->ubo->bind_uniform();
m_ubs.projection = glm::mat4(glm::mat3(glm::translate(glm::scale(glm::mat3(), 1.0f / window->scale), window->offset)));
window->ubo->update(m_ubs);
if (image) {
glTexImage2D(
GL_TEXTURE_2D, 0,
format,
width, height, 0,
components, GL_UNSIGNED_BYTE, image);
delete[] image;
if (Global.python_mipmaps) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glGenerateMipmap(GL_TEXTURE_2D);
}
else {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
}
}
glViewport(0, 0, window->fb_size.x, window->fb_size.y);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glfwSwapBuffers(window->window);
}
auto frametime = std::chrono::high_resolution_clock::now() - start_time;
if ((Global.python_minframetime - frametime).count() > 0.0f)
std::this_thread::sleep_for(Global.python_minframetime - frametime);
}
}
void python_screen_viewer::notify_window_fb_size(GLFWwindow *window, int w, int h)
{
for (auto &conf : m_windows) {
if (conf->window == window) {
conf->fb_size.x = w;
conf->fb_size.y = h;
return;
}
}
}
void python_screen_viewer::notify_window_size(GLFWwindow *window, int w, int h)
{
for (auto &conf : m_windows) {
if (conf->window == window) {
conf->window_size.x = w;
conf->window_size.y = h;
return;
}
}
}
void python_screen_viewer::notify_cursor_pos(GLFWwindow *window, double x, double y)
{
for (auto &conf : m_windows) {
if (conf->window == window) {
conf->cursor_pos.x = x;
conf->cursor_pos.y = y;
return;
}
}
}
void python_screen_viewer::notify_click(GLFWwindow *window, int button, int action)
{
if (button != GLFW_MOUSE_BUTTON_LEFT || action != GLFW_PRESS)
return;
for (auto &conf : m_windows) {
if (conf->window == window) {
auto pos = glm::vec2(conf->cursor_pos) / glm::vec2(conf->window_size);
pos.y = 1.0f - pos.y;
pos = (pos + conf->offset) / conf->scale;
m_touchlist->emplace_back(pos);
return;
}
}
}
python_screen_viewer::window_state::~window_state()
{
if (!window)
return;
if (!Global.python_sharectx) {
GLFWwindow *current = glfwGetCurrentContext();
glfwMakeContextCurrent(window);
ubo = nullptr;
shader = nullptr;
gl::program::unbind();
gl::buffer::unbind();
glfwMakeContextCurrent(current);
}
glfwDestroyWindow(window);
}

View File

@@ -0,0 +1,45 @@
#include "renderer.h"
#include "PyInt.h"
#include <GLFW/glfw3.h>
class python_screen_viewer
{
struct window_state {
GLFWwindow *window = nullptr;
glm::ivec2 window_size;
glm::ivec2 fb_size;
glm::ivec2 cursor_pos;
glm::vec2 offset;
glm::vec2 scale;
std::unique_ptr<gl::ubo> ubo;
std::unique_ptr<gl::program> shader;
std::chrono::high_resolution_clock::time_point timestamp;
window_state() = default;
~window_state();
};
std::vector<std::unique_ptr<window_state>> m_windows;
std::shared_ptr<python_rt> m_rt;
std::shared_ptr<std::vector<glm::vec2>> m_touchlist;
std::shared_ptr<std::thread> m_renderthread;
gl::scene_ubs m_ubs;
std::atomic_bool m_exit = false;
void threadfunc();
public:
python_screen_viewer(std::shared_ptr<python_rt> rt, std::shared_ptr<std::vector<glm::vec2>> touchlist, std::string name);
~python_screen_viewer();
void notify_window_size(GLFWwindow *window, int w, int h);
void notify_window_fb_size(GLFWwindow *window, int w, int h);
void notify_cursor_pos(GLFWwindow *window, double x, double y);
void notify_click(GLFWwindow *window, int button, int action);
};