This commit is contained in:
Benedek László 2024-05-15 22:52:13 +02:00
parent 911a174006
commit 9d27db7777
14 changed files with 333 additions and 110 deletions

3
.gitignore vendored
View File

@ -1,2 +1,3 @@
build build
.cache .cache
__pycache__

View File

@ -5,6 +5,6 @@
extern "C" { extern "C" {
extern plugin_t plugin; extern plugin_t plugin;
bool init(); int run();
void destroy(); int destroy();
} }

View File

@ -12,7 +12,7 @@ typedef struct {
} point_t; } point_t;
typedef int (*run_method_t)(); typedef int (*run_method_t)();
typedef void (*destroy_t)(); typedef int (*destroy_t)();
typedef void (*update_callback_t)(unsigned int chartIndex, const char* seriesName, const point_t* points, int count); typedef void (*update_callback_t)(unsigned int chartIndex, const char* seriesName, const point_t* points, int count);
typedef struct { typedef struct {
@ -20,4 +20,6 @@ typedef struct {
run_method_t run_method; run_method_t run_method;
destroy_t destroy_method; destroy_t destroy_method;
update_callback_t update_callback; update_callback_t update_callback;
int argc;
char** argv;
} plugin_t; } plugin_t;

View File

@ -2,12 +2,11 @@
#include <graph/server/gui/chart/chart.h> #include <graph/server/gui/chart/chart.h>
#include <graph/server/plugin.h> #include <graph/server/plugin.h>
#include <qfiledialog.h> #include <QFileDialog>
#include <QMainWindow> #include <QMainWindow>
#include <QMenuBar> #include <QMenuBar>
#include <QStatusBar> #include <QStatusBar>
#include <QToolBar> #include <QToolBar>
#include <cstddef>
#include <vector> #include <vector>
namespace Ui { namespace Ui {
@ -18,13 +17,14 @@ namespace Graph::GUI {
class MainWindow : public QMainWindow { class MainWindow : public QMainWindow {
Q_OBJECT Q_OBJECT
private: private:
explicit MainWindow(QWidget* parent = nullptr); explicit MainWindow(QWidget* parent = nullptr, int argc = 0, char** argv = nullptr);
public: public:
MainWindow(MainWindow& other) = delete; MainWindow(MainWindow& other) = delete;
void operator=(const MainWindow&) = delete; void operator=(const MainWindow&) = delete;
~MainWindow(); ~MainWindow();
static MainWindow* getInstance(int argc, char** argv);
static MainWindow* getInstance(); static MainWindow* getInstance();
static void close(); static void close();
@ -42,5 +42,8 @@ class MainWindow : public QMainWindow {
std::vector<ChartWidget*> charts; std::vector<ChartWidget*> charts;
QFileDialog* fileDialog; QFileDialog* fileDialog;
int argc;
char** argv;
}; };
} // namespace Graph::GUI } // namespace Graph::GUI

View File

@ -22,7 +22,7 @@ class Plugin : public QObject {
void exited(int code); void exited(int code);
public: public:
Plugin(const std::string& path, update_callback_t callback); Plugin(const std::string& path, update_callback_t callback, int argc, char** argv);
~Plugin(); ~Plugin();
const std::string& getPath() const { return this->path; } const std::string& getPath() const { return this->path; }

View File

@ -13,6 +13,8 @@ struct chart_spec_t {
QString title; QString title;
bool grid = false; bool grid = false;
bool legend = false; bool legend = false;
bool keep_all = true;
int keep_limit;
struct { struct {
axis_spec_t x, y; axis_spec_t x, y;
} axis; } axis;

View File

@ -1,3 +1,5 @@
file(GLOB_RECURSE HTTP_SOURCES "./*.cpp") file(GLOB_RECURSE HTTP_SOURCES "./*.cpp")
add_library(http SHARED ${HTTP_SOURCES}) add_library(http SHARED ${HTTP_SOURCES})
target_link_libraries(http librum.a)
# target_link_libraries(http librum.a libflags-cpp.so)
set_target_properties(http PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/plugins") set_target_properties(http PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/plugins")

View File

@ -1,28 +1,72 @@
#include <graph/plugins/http/http.h> #include <graph/plugins/http/http.h>
#include <graph/plugins/plugin.h> #include <graph/plugins/plugin.h>
#include <cmath> #include <rum/http/response.h>
#include <rum/http/server.h>
#include <rum/http/uri.h>
#include <rum/tcp/error.h>
// #include <flags-cpp/flags-cpp.h>
#include <iostream> #include <iostream>
#include <sstream>
#define PI 3.1415 using namespace Rum::HTTP;
#define to_rad(x) (x / 180 / 3.1415) // using namespace Flags;
Server* server;
extern "C" { extern "C" {
plugin_t plugin = {.version = 1, .run_method = &run, .destroy_method = &destroy};
int run() { int run() {
std::cout << "init" << std::endl; // Parser parser;
point_t points[100]; // int* port = parser.add<int>("port", "http port", false, 8080);
for (int i = 0; i < 100; i++) { // int* workers = parser.add<int>("workers", "number of worker threads", false, 10);
points[i].x = i;
points[i].y = i;
}
plugin.update_callback(0, "1st", points, 100); // bool* help = parser.add<bool>("help", "print help", false, false);
// if (!parser.parse(plugin.argc, plugin.argv) || *help) {
// parser.help();
// }
// server = new Server(*port, *workers, DEFAULT_BUFFER_SIZE);
try {
server = new Server(8080, 10, DEFAULT_BUFFER_SIZE);
server->add_path<GET>("/add", [](const Request& req, Response& resp) {
URI uri = req.copy_uri();
auto params = uri.get_parameters();
int chart = 0;
if (params.contains("chart"))
chart = std::stoi(params.at("chart"));
std::string series = "1st";
if (params.contains("series"))
series = params.at("series");
if (params.contains("x") && params.contains("y")) {
point_t point;
point.x = std::stod(params.at("x"));
point.y = std::stod(params.at("y"));
plugin.update_callback(chart, series.c_str(), &point, 1);
}
});
std::cout << "starting http server" << std::endl;
server->listen();
return 0;
} catch (Rum::TCP::Error e) {
std::cerr << e.what() << std::endl;
delete server;
return 1;
}
}
int destroy() {
if (server)
server->end();
std::cout << "stopped http server" << std::endl;
return 0; return 0;
} }
void destroy() {
std::cout << "destory" << std::endl;
}
plugin_t plugin = {.version = 1, .run_method = &run, .destroy_method = &destroy};
} }

View File

@ -4,8 +4,7 @@
#include <graph/server/plugin.h> #include <graph/server/plugin.h>
#include <graph/server/spec.h> #include <graph/server/spec.h>
#include <graph/server/ui_mainwindow.h> #include <graph/server/ui_mainwindow.h>
#include <qobject.h> #include <qlineseries.h>
#include <qthread.h>
#include <QFileDialog> #include <QFileDialog>
#include <QMessageBox> #include <QMessageBox>
#include <iostream> #include <iostream>
@ -18,6 +17,13 @@ namespace Graph::GUI {
MainWindow* MainWindow::instance = nullptr; MainWindow* MainWindow::instance = nullptr;
MainWindow* MainWindow::getInstance(int argc, char** argv) {
if (!instance)
instance = new MainWindow(nullptr, argc, argv);
return instance;
}
MainWindow* MainWindow::getInstance() { MainWindow* MainWindow::getInstance() {
if (!instance) if (!instance)
instance = new MainWindow(); instance = new MainWindow();
@ -46,30 +52,38 @@ void MainWindow::addData(unsigned int chartIndex, const char* seriesName, const
double minX = std::numeric_limits<double>::max(), maxX = std::numeric_limits<double>::min(); double minX = std::numeric_limits<double>::max(), maxX = std::numeric_limits<double>::min();
double minY = std::numeric_limits<double>::max(), maxY = std::numeric_limits<double>::min(); double minY = std::numeric_limits<double>::max(), maxY = std::numeric_limits<double>::min();
chart_spec_t spec = chart->getSpec();
for (QAbstractSeries* series : chart->getSeries()) { for (QAbstractSeries* series : chart->getSeries()) {
if (series->name() == name) { if (series->name() == name) {
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
*((QLineSeries*)series) << QPointF(points[i].x, points[i].y); *((QLineSeries*)series) << QPointF(points[i].x, points[i].y);
} }
}
for (QPointF point : ((QLineSeries*)series)->points()) { if (!spec.keep_all && ((QLineSeries*)series)->count() > spec.keep_limit) {
minX = MIN(minX, point.x()); ((QLineSeries*)series)->removePoints(0, ((QLineSeries*)series)->count() - spec.keep_limit);
maxX = MAX(maxX, point.x()); }
minY = MIN(minY, point.y());
maxY = MAX(maxY, point.y()); for (QPointF point : ((QLineSeries*)series)->points()) {
minX = MIN(minX, point.x());
maxX = MAX(maxX, point.x());
minY = MIN(minY, point.y());
maxY = MAX(maxY, point.y());
}
if (spec.axis.x.auto_limit)
chart->getX()->setRange(minX, maxX);
if (spec.axis.y.auto_limit)
chart->getY()->setRange(minY, maxY);
break;
} }
} }
if (chart->getSpec().axis.x.auto_limit)
chart->getX()->setRange(minX, maxX);
if (chart->getSpec().axis.y.auto_limit)
chart->getY()->setRange(minY, maxY);
chart->getView()->update(); chart->getView()->update();
} }
MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWindow) { MainWindow::MainWindow(QWidget* parent, int argc, char** argv) : QMainWindow(parent), ui(new Ui::MainWindow), argc(argc), argv(argv) {
ui->setupUi(this); ui->setupUi(this);
// plugins // plugins
@ -126,8 +140,19 @@ void MainWindow::on_action_Load_triggered() {
return; return;
for (QString file : fileDialog->selectedFiles()) { for (QString file : fileDialog->selectedFiles()) {
bool loaded = false;
for (Plugin* plugin : pluginModel->getPlugins()) {
if (plugin->getPath() == file.toStdString()) {
loaded = true;
}
}
if (loaded) {
QMessageBox::critical(this, "Error", "Plugin already loaded:\n" + file, QMessageBox::StandardButton::Ignore);
continue;
}
try { try {
Plugin* plugin = new Plugin(file.toStdString(), &Graph::GUI::addData); Plugin* plugin = new Plugin(file.toStdString(), &Graph::GUI::addData, argc, argv);
pluginModel->add(plugin); pluginModel->add(plugin);
QThread* thread = new QThread(); QThread* thread = new QThread();
@ -135,11 +160,15 @@ void MainWindow::on_action_Load_triggered() {
connect(plugin, &Plugin::exited, this, [](int code) { std::cout << "plugin exited: " << code << std::endl; }); connect(plugin, &Plugin::exited, this, [](int code) { std::cout << "plugin exited: " << code << std::endl; });
connect(thread, &QThread::started, plugin, &Plugin::run); connect(thread, &QThread::started, plugin, &Plugin::run);
connect(plugin, &Plugin::exited, thread, &QThread::quit); connect(plugin, &Plugin::exited, thread, &QThread::quit);
connect(plugin, &Plugin::exited, this, [this, file](int code) {
if (code != 0)
QMessageBox::critical(this, "Error", "Plugin exited with an error!\n" + file, QMessageBox::StandardButton::Ignore);
});
connect(thread, &QThread::finished, thread, &QThread::deleteLater); connect(thread, &QThread::finished, thread, &QThread::deleteLater);
thread->start(); thread->start();
} catch (std::runtime_error e) { } catch (std::runtime_error e) {
std::cout << e.what() << std::endl; std::cout << e.what() << std::endl;
QMessageBox::critical(this, "Error", "Failed to load plugin:\n" + file, QMessageBox::StandardButton::Ignore); QMessageBox::critical(this, "Error", "Failed to load plugin!\n" + file, QMessageBox::StandardButton::Ignore);
} }
int row = pluginModel->rowCount(); int row = pluginModel->rowCount();

View File

@ -13,7 +13,7 @@ SpecDialog* SpecDialog::getInstance() {
} }
void SpecDialog::close() { void SpecDialog::close() {
if(instance) if (instance)
delete instance; delete instance;
} }
@ -41,12 +41,12 @@ SpecDialog::~SpecDialog() {
} }
chart_spec_t SpecDialog::getSpec() const { chart_spec_t SpecDialog::getSpec() const {
return { return {.title = ui->chartTitle->text(),
.title = ui->chartTitle->text(), .grid = ui->grid->isChecked(),
.grid = (ui->grid->checkState() == Qt::Checked), .legend = ui->legend->isChecked(),
.legend = (ui->legend->checkState() == Qt::Checked), .keep_all = ui->keepAll->isChecked(),
.axis = { .keep_limit = ui->keep->value(),
.x = {.label = ui->xTitle->text(), .auto_limit = (ui->xAutoRange->checkState() == Qt::Checked), .min = ui->xMin->value(), .max = ui->xMax->value()}, .axis = {.x = {.label = ui->xTitle->text(), .auto_limit = ui->xAutoRange->isChecked(), .min = ui->xMin->value(), .max = ui->xMax->value()},
.y = {.label = ui->yTitle->text(), .auto_limit = (ui->yAutoRange->checkState() == Qt::Checked), .min = ui->yMin->value(), .max = ui->yMax->value()}}}; .y = {.label = ui->yTitle->text(), .auto_limit = ui->yAutoRange->isChecked(), .min = ui->yMin->value(), .max = ui->yMax->value()}}};
} }
} // namespace Graph::GUI } // namespace Graph::GUI

View File

@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>302</width> <width>266</width>
<height>372</height> <height>334</height>
</rect> </rect>
</property> </property>
<property name="sizePolicy"> <property name="sizePolicy">
@ -70,11 +70,17 @@
<item alignment="Qt::AlignTop"> <item alignment="Qt::AlignTop">
<widget class="QFrame" name="frame"> <widget class="QFrame" name="frame">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred"> <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch> <horstretch>0</horstretch>
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>
</property> </property>
<property name="acceptDrops">
<bool>false</bool>
</property>
<property name="autoFillBackground">
<bool>false</bool>
</property>
<property name="frameShape"> <property name="frameShape">
<enum>QFrame::StyledPanel</enum> <enum>QFrame::StyledPanel</enum>
</property> </property>
@ -96,41 +102,84 @@
<height>16</height> <height>16</height>
</size> </size>
</property> </property>
<property name="autoFillBackground">
<bool>false</bool>
</property>
<property name="text"> <property name="text">
<string>X</string> <string>X</string>
</property> </property>
<property name="scaledContents">
<bool>false</bool>
</property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QLineEdit" name="xTitle"> <widget class="QLineEdit" name="xTitle">
<property name="autoFillBackground">
<bool>false</bool>
</property>
<property name="frame">
<bool>true</bool>
</property>
<property name="placeholderText"> <property name="placeholderText">
<string>Axis Title</string> <string>Axis Title</string>
</property> </property>
</widget> <property name="clearButtonEnabled">
</item> <bool>false</bool>
<item>
<widget class="QCheckBox" name="xAutoRange">
<property name="text">
<string>Auto-Range</string>
</property>
<property name="checked">
<bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QDoubleSpinBox" name="xMin"> <widget class="QFrame" name="frame_3">
<property name="toolTip"> <property name="sizePolicy">
<string>minimum</string> <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property> </property>
</widget> <property name="autoFillBackground">
</item> <bool>false</bool>
<item> </property>
<widget class="QDoubleSpinBox" name="xMax"> <property name="frameShape">
<property name="toolTip"> <enum>QFrame::StyledPanel</enum>
<string> </property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Range</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="xAutoRange">
<property name="text">
<string>Auto</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="xMin">
<property name="toolTip">
<string>minimum</string>
</property>
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="xMax">
<property name="toolTip">
<string>
&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;maximum&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> &lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;maximum&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
</widget>
</item>
</layout>
</widget> </widget>
</item> </item>
</layout> </layout>
@ -144,6 +193,9 @@
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>
</property> </property>
<property name="autoFillBackground">
<bool>false</bool>
</property>
<property name="frameShape"> <property name="frameShape">
<enum>QFrame::StyledPanel</enum> <enum>QFrame::StyledPanel</enum>
</property> </property>
@ -178,29 +230,48 @@
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QCheckBox" name="yAutoRange"> <widget class="QFrame" name="frame_4">
<property name="text"> <property name="frameShape">
<string>Auto-Range</string> <enum>QFrame::StyledPanel</enum>
</property> </property>
<property name="checked"> <property name="frameShadow">
<bool>true</bool> <enum>QFrame::Raised</enum>
</property> </property>
</widget> <layout class="QVBoxLayout" name="verticalLayout_3">
</item> <item>
<item> <widget class="QLabel" name="label_4">
<widget class="QDoubleSpinBox" name="yMin"> <property name="text">
<property name="toolTip"> <string>Range</string>
<string> </property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="yAutoRange">
<property name="text">
<string>Auto</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="yMin">
<property name="toolTip">
<string>
&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;minimum&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> &lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;minimum&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QDoubleSpinBox" name="yMax"> <widget class="QDoubleSpinBox" name="yMax">
<property name="toolTip"> <property name="toolTip">
<string> <string>
&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;maximum&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> &lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;maximum&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
</widget>
</item>
</layout>
</widget> </widget>
</item> </item>
</layout> </layout>
@ -210,7 +281,7 @@
</widget> </widget>
<widget class="QWidget" name="displayTab"> <widget class="QWidget" name="displayTab">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum"> <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch> <horstretch>0</horstretch>
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>
@ -219,24 +290,75 @@
<string>Display</string> <string>Display</string>
</attribute> </attribute>
<layout class="QVBoxLayout" name="displayVerticalLayout"> <layout class="QVBoxLayout" name="displayVerticalLayout">
<item> <item alignment="Qt::AlignTop">
<widget class="QCheckBox" name="legend"> <widget class="QWidget" name="widget_2" native="true">
<property name="text"> <property name="sizePolicy">
<string>Legend</string> <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
</property> <horstretch>0</horstretch>
<property name="checked"> <verstretch>0</verstretch>
<bool>true</bool> </sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="grid">
<property name="text">
<string>Grid</string>
</property>
<property name="checked">
<bool>true</bool>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<widget class="QCheckBox" name="legend">
<property name="text">
<string>Legend</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="grid">
<property name="text">
<string>Grid</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QFrame" name="frame_5">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>Keep</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="keepAll">
<property name="text">
<string>Keep All</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="keep">
<property name="maximum">
<number>2147483647</number>
</property>
<property name="value">
<number>1000</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget> </widget>
</item> </item>
</layout> </layout>

View File

@ -3,7 +3,7 @@
int main(int argc, char** argv) { int main(int argc, char** argv) {
QApplication app(argc, argv); QApplication app(argc, argv);
Graph::GUI::MainWindow::getInstance()->show(); Graph::GUI::MainWindow::getInstance(argc, argv)->show();
int result = app.exec(); int result = app.exec();
Graph::GUI::MainWindow::close(); Graph::GUI::MainWindow::close();
return result; return result;

View File

@ -6,7 +6,7 @@
#include <string> #include <string>
namespace Graph { namespace Graph {
Plugin::Plugin(const std::string& path, update_callback_t callback) : path(path) { Plugin::Plugin(const std::string& path, update_callback_t callback, int argc, char** argv) : path(path) {
handle = dlopen(path.c_str(), RTLD_LAZY); handle = dlopen(path.c_str(), RTLD_LAZY);
if (!handle) if (!handle)
throw std::runtime_error(dlerror()); throw std::runtime_error(dlerror());
@ -16,6 +16,8 @@ Plugin::Plugin(const std::string& path, update_callback_t callback) : path(path)
throw std::runtime_error(dlerror()); throw std::runtime_error(dlerror());
plugin->update_callback = callback; plugin->update_callback = callback;
plugin->argc = argc;
plugin->argv = argv;
} }
void Plugin::run() { void Plugin::run() {
@ -24,7 +26,7 @@ void Plugin::run() {
Plugin::~Plugin() { Plugin::~Plugin() {
if (plugin->destroy_method) if (plugin->destroy_method)
plugin->destroy_method(); emit exited(plugin->destroy_method());
if (handle) if (handle)
dlclose((void*)handle); dlclose((void*)handle);

16
test/test.py Executable file
View File

@ -0,0 +1,16 @@
#!/bin/env python3
import requests
from math import sin
from time import sleep
def main():
x = 0.0
while True:
requests.get(f"http://localhost:8080/add?chart=0&series=R1&x={x}&y={sin(x)}")
x += 0.01
sleep(0.01)
if __name__ == "__main__":
main()