mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-07-20 10:19:19 +02:00
Reorganize source files into logical subdirectories
Co-authored-by: Hirek193 <23196899+Hirek193@users.noreply.github.com>
This commit is contained in:
626
scripting/PyInt.cpp
Normal file
626
scripting/PyInt.cpp
Normal 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
159
scripting/PyInt.h
Normal 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
42
scripting/PyIntStub.cpp
Normal 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
400
scripting/ladderlogic.cpp
Normal 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
169
scripting/ladderlogic.h
Normal 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
212
scripting/lua.cpp
Normal 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
22
scripting/lua.h
Normal 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
96
scripting/lua_ffi.h
Normal 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";
|
||||
258
scripting/pythonscreenviewer.cpp
Normal file
258
scripting/pythonscreenviewer.cpp
Normal 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);
|
||||
}
|
||||
45
scripting/pythonscreenviewer.h
Normal file
45
scripting/pythonscreenviewer.h
Normal 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);
|
||||
};
|
||||
Reference in New Issue
Block a user