diff options
author | Christian Kamm <[email protected]> | 2010-02-26 13:57:50 +0100 |
---|---|---|
committer | Christian Kamm <[email protected]> | 2010-02-26 13:57:50 +0100 |
commit | 9a52f0dd19a1d3314b435fade0a5338609e838a1 (patch) | |
tree | 9be84e54fdc7b375b2778705659176d6ecbfc6b5 | |
parent | 754a63ff40fd78be7bc0bf70c8b072d079e1fac9 (diff) |
-rw-r--r-- | example/example.pro | 2 | ||||
-rw-r--r-- | example/main.cpp | 11 | ||||
-rw-r--r-- | library/components/scriptui.cpp | 153 | ||||
-rw-r--r-- | library/components/scriptui.h | 38 | ||||
-rw-r--r-- | library/fibers/fiber.cpp | 120 | ||||
-rw-r--r-- | library/fibers/fiber.h | 48 | ||||
-rw-r--r-- | library/fibers/fibers.pri | 43 | ||||
-rw-r--r-- | library/fibers/initializestack_32.cpp | 21 | ||||
-rw-r--r-- | library/fibers/initializestack_64_linux_mac.cpp | 22 | ||||
-rw-r--r-- | library/fibers/initializestack_64_win.cpp | 44 | ||||
-rw-r--r-- | library/fibers/switchstack_gcc_32_linux_mac.s | 41 | ||||
-rw-r--r-- | library/fibers/switchstack_gcc_32_win.s | 42 | ||||
-rw-r--r-- | library/fibers/switchstack_gcc_64_linux_mac.s | 44 | ||||
-rw-r--r-- | library/fibers/switchstack_gcc_64_win.s | 93 | ||||
-rw-r--r-- | library/fibers/switchstack_msvc_32.cpp | 44 | ||||
-rw-r--r-- | library/fibers/switchstack_msvc_64.asm | 98 | ||||
-rw-r--r-- | library/remotecontrolwidget.pro | 10 | ||||
-rw-r--r-- | library/scriptadapter.cpp | 214 | ||||
-rw-r--r-- | library/scriptadapter.h | 90 |
19 files changed, 1173 insertions, 5 deletions
diff --git a/example/example.pro b/example/example.pro index 9b8f895..b1fd669 100644 --- a/example/example.pro +++ b/example/example.pro @@ -4,7 +4,7 @@ # #------------------------------------------------- -QT += core gui +QT += core gui script TARGET = example TEMPLATE = app diff --git a/example/main.cpp b/example/main.cpp index e8716bc..e66f03a 100644 --- a/example/main.cpp +++ b/example/main.cpp @@ -1,5 +1,7 @@ #include <QtGui/QApplication> #include "remotecontrolwidget.h" +#include "scriptadapter.h" +#include "components/scriptui.h" #include "components/locationui.h" #include "components/batterybutton.h" #include "components/powerbutton.h" @@ -9,8 +11,8 @@ int main(int argc, char *argv[]) QApplication a(argc, argv); RemoteControlWidget w; - LocationUi l; - w.addToolBoxPage(&l); + LocationUi locationUi; + w.addToolBoxPage(&locationUi); BatteryButton batteryButton; w.addMenuButton(&batteryButton); @@ -18,6 +20,11 @@ int main(int argc, char *argv[]) PowerButton powerButton; w.addMenuButton(&powerButton); + ScriptAdapter adapter; + adapter.addScriptInterface("location", locationUi.scriptInterface()); + ScriptUi scriptUi(&adapter); + w.addToolBoxPage(&scriptUi); + w.show(); return a.exec(); } diff --git a/library/components/scriptui.cpp b/library/components/scriptui.cpp new file mode 100644 index 0000000..43ed447 --- /dev/null +++ b/library/components/scriptui.cpp @@ -0,0 +1,153 @@ +#include "scriptui.h" + +#include "scriptadapter.h" +#include "optionsitem.h" + +#include <QtCore/QCoreApplication> +#include <QtCore/QSignalMapper> +#include <QtGui/QGridLayout> +#include <QtGui/QAbstractItemView> +#include <QtGui/QPushButton> +#include <QtGui/QHeaderView> +#include <QtGui/QTableWidget> +#include <QtGui/QTreeView> +#include <QtGui/QFileSystemModel> +#include <QtGui/QToolButton> +#include <QtGui/QMenu> + +ScriptUi::ScriptUi(ScriptAdapter *adapter, QWidget *parent) + : ToolBoxPage(parent) + , mAdapter(adapter) +{ + QStringList tags; + QList<OptionsItem *> options; + tags << tr("scripting"); + + QGridLayout *scriptingLayout = new QGridLayout(); + { + mScriptList = new QTableWidget(0, 2); + mScriptList->verticalHeader()->setHidden(true); + QStringList headerLabels; + headerLabels << tr("Active scripts") << tr("Status"); + mScriptList->setHorizontalHeaderLabels(headerLabels); + mScriptList->horizontalHeader()->setStretchLastSection(false); + mScriptList->horizontalHeader()->setResizeMode(0, QHeaderView::Stretch); + mScriptList->horizontalHeader()->setResizeMode(1, QHeaderView::Fixed); + mScriptList->setSelectionMode(QAbstractItemView::SingleSelection); + mScriptList->setSelectionBehavior(QAbstractItemView::SelectRows); + + scriptingLayout->addWidget(mScriptList, 1, 0); + + QVBoxLayout *scriptListButtonLayout = new QVBoxLayout(); + { + QPushButton *abortButton = new QPushButton(tr("Abort")); + QPushButton *pauseButton = new QPushButton(tr("Pause")); + connect(abortButton, SIGNAL(clicked()), this, SLOT(abortScript())); + connect(pauseButton, SIGNAL(clicked()), this, SLOT(togglePauseScript())); + + scriptListButtonLayout->addWidget(pauseButton); + scriptListButtonLayout->addWidget(abortButton); + scriptListButtonLayout->addStretch(); + } + scriptingLayout->addLayout(scriptListButtonLayout, 1, 1); + + mFileView = new QTreeView(); + mFileModel = new QFileSystemModel(mFileView); + QDir scriptsDir(QCoreApplication::applicationDirPath()); + scriptsDir.cd(QLatin1String("scripts")); + QStringList nameFilters; + nameFilters << "*.js" << "*.qs"; + mFileModel->setNameFilters(nameFilters); + mFileModel->setNameFilterDisables(false); + mFileModel->setRootPath(scriptsDir.path()); + mFileView->setModel(mFileModel); + mFileView->setColumnHidden(1, true); + mFileView->setColumnHidden(2, true); + mFileView->setColumnHidden(3, true); + mFileView->setHeaderHidden(true); + mFileView->setRootIndex(mFileModel->index(scriptsDir.path())); + connect(mFileView, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(runSelectedScript())); + + scriptingLayout->addWidget(mFileView, 0, 0); + + QVBoxLayout *scriptRunnerButtonLayout = new QVBoxLayout(); + { + QPushButton *runButton = new QPushButton(tr("Run")); + connect(runButton, SIGNAL(clicked()), this, SLOT(runSelectedScript())); + + scriptRunnerButtonLayout->addWidget(runButton); + scriptRunnerButtonLayout->addStretch(); + } + scriptingLayout->addLayout(scriptRunnerButtonLayout, 0, 1); + } + + QWidget *tmp = new QWidget(); + tmp->setLayout(scriptingLayout); + + OptionsItem *item = new OptionsItem("", tmp, true); + item->setTags(tags); + options << item; + + connect(mAdapter, SIGNAL(scriptStart(ScriptFiber*)), this, SLOT(addScript(ScriptFiber*))); + connect(mAdapter, SIGNAL(scriptStop(int)), this, SLOT(removeScript(int))); + + setTitle(tr("Scripting")); + setOptions(options); +} + +void ScriptUi::runScript(const QString &filePath) +{ + mAdapter->run(filePath); +} + +void ScriptUi::runSelectedScript() +{ + QModelIndex index = mFileView->currentIndex(); + + if (index.isValid() && !mFileModel->isDir(index)) { + QString scriptFilePath = mFileModel->filePath(index); + runScript(scriptFilePath); + } +} + +void ScriptUi::abortScript() +{ + if (!mScriptList->currentItem()) + return; + + ScriptFiber *script = mAdapter->script(mScriptList->currentIndex().row()); + if (!script) + return; + + script->requestTerminate(); + mScriptList->item(mScriptList->currentItem()->row(), 1)->setText(tr("aborting")); +} + +void ScriptUi::togglePauseScript() +{ + if (!mScriptList->currentItem()) + return; + + ScriptFiber *script = mAdapter->script(mScriptList->currentIndex().row()); + if (!script) + return; + + script->togglePause(); + if (script->isPaused()) + mScriptList->item(mScriptList->currentItem()->row(), 1)->setText(tr("paused")); + else + mScriptList->item(mScriptList->currentItem()->row(), 1)->setText(tr("running")); +} + +void ScriptUi::addScript(ScriptFiber *script) +{ + int newRow = mScriptList->rowCount(); + mScriptList->insertRow(newRow); + mScriptList->setItem(newRow, 0, new QTableWidgetItem(script->name())); + mScriptList->setItem(newRow, 1, new QTableWidgetItem(tr("running"))); +} + +void ScriptUi::removeScript(int index) +{ + mScriptList->removeRow(index); +} diff --git a/library/components/scriptui.h b/library/components/scriptui.h new file mode 100644 index 0000000..80f9b98 --- /dev/null +++ b/library/components/scriptui.h @@ -0,0 +1,38 @@ +#ifndef SCRIPTUI_H +#define SCRIPTUI_H + +#include "remotecontrolwidget_global.h" +#include "toolbox.h" + +#include <QtCore/QObject> + +class ScriptAdapter; +class ScriptFiber; +class QTreeView; +class QFileSystemModel; +class QTableWidget; + +class REMOTECONTROLWIDGETSHARED_EXPORT ScriptUi : public ToolBoxPage +{ + Q_OBJECT +public: + ScriptUi(ScriptAdapter *adapter, QWidget *parent = 0); + +private slots: + void runScript(const QString &filePath); + void runSelectedScript(); + void abortScript(); + void togglePauseScript(); + + void addScript(ScriptFiber *script); + void removeScript(int index); + +private: + ScriptAdapter *mAdapter; + + QTreeView *mFileView; + QFileSystemModel *mFileModel; + QTableWidget *mScriptList; +}; + +#endif // SCRIPTUI_H diff --git a/library/fibers/fiber.cpp b/library/fibers/fiber.cpp new file mode 100644 index 0000000..7208ced --- /dev/null +++ b/library/fibers/fiber.cpp @@ -0,0 +1,120 @@ +#include <stdlib.h> +#include <QtCore/QtGlobal> + +#include "fiber.h" + +/*! + \class Fiber + \brief The Fiber class provides cooperatively scheduled stacks of execution. + + Fibers, also known as coroutines, allow managing multiple stacks in the same + thread. + + To create a fiber, subclass Fiber and override the run() method. To run it, + call cont(). This will execute the code in run() until it calls Fiber::yield(). + At that point, the call to cont() returns. Subsequent calls to cont() will + continue execution of the fiber just after the yield(). + + Example: + class MyFiber : public Fiber + { + virtual void run() + { + qDebug() << "1"; + Fiber::yield(); + qDebug() << "2"; + } + } + + MyFiber fib; + qDebug() << "0.5"; + fib.cont(); // prints 1 + qDebug() << "1.5"; + fib.cont(); // prints 2 +*/ + +#ifdef Q_OS_MAC +extern "C" void switchStackInternal(void* to, void** from); +void initializeStack(void *data, int size, void (*entry)(), void **stackPointer); +void switchStack(void* to, void** from) { switchStackInternal(to, from); } +#else +extern "C" void _switchStackInternal(void* to, void** from); +void initializeStack(void *data, int size, void (*entry)(), void **stackPointer); +void switchStack(void* to, void** from) { _switchStackInternal(to, from); } +#endif + +Fiber *Fiber::_currentFiber = 0; + +Fiber::Fiber(int stackSize) + : _stackData(0) + , _stackPointer(0) + , _previousFiber(0) + , _status(NotStarted) +{ + // establish starting fiber context if necessary + currentFiber(); + + _stackData = malloc(stackSize); + initializeStack(_stackData, stackSize, &entryPoint, &_stackPointer); +} + +Fiber::Fiber(bool) + : _stackData(0) + , _stackPointer(0) + , _previousFiber(0) + , _status(Running) +{ +} + +Fiber::~Fiber() +{ + if (_stackData) + free(_stackData); +} + +Fiber *Fiber::currentFiber() +{ + // establish a context for the starting fiber + if (!_currentFiber) + _currentFiber = new Fiber(true); + + return _currentFiber; +} + +void Fiber::entryPoint() +{ + _currentFiber->run(); + yieldHelper(Terminated); + Q_ASSERT(0); // unreachable +} + +// returns whether it can be continued again +bool Fiber::cont() +{ + Q_ASSERT(_status == NotStarted || _status == Stopped); + Q_ASSERT(!_previousFiber); + + _previousFiber = _currentFiber; + _currentFiber = this; + _status = Running; + switchStack(_stackPointer, &_previousFiber->_stackPointer); + return _status != Terminated; +} + +void Fiber::yield() +{ + yieldHelper(Stopped); +} + +void Fiber::yieldHelper(Status stopStatus) +{ + Fiber *stoppingFiber = _currentFiber; + Q_ASSERT(stoppingFiber); + Q_ASSERT(stoppingFiber->_previousFiber); + Q_ASSERT(stoppingFiber->_status == Running); + + _currentFiber = stoppingFiber->_previousFiber; + stoppingFiber->_previousFiber = 0; + stoppingFiber->_status = stopStatus; + switchStack(_currentFiber->_stackPointer, &stoppingFiber->_stackPointer); +} diff --git a/library/fibers/fiber.h b/library/fibers/fiber.h new file mode 100644 index 0000000..6afc57d --- /dev/null +++ b/library/fibers/fiber.h @@ -0,0 +1,48 @@ +#ifndef INCLUDE_FIBER_H +#define INCLUDE_FIBER_H + +class Fiber +{ +public: + enum Status + { + NotStarted, + Running, + Stopped, + Terminated + }; + +public: + explicit Fiber(int stackSize = 32768); + virtual ~Fiber(); + + bool cont(); + static void yield(); + + static Fiber *currentFiber(); + + Status status() + { return _status; } + +protected: + // could be abstract if subclassing for start fiber + virtual void run() {} + +private: + // for the original fiber + Fiber(bool); + + static void yieldHelper(Status stopStatus); + + void *_stackData; + void *_stackPointer; + Fiber *_previousFiber; + Status _status; + + // should be thread local + static Fiber *_currentFiber; + + static void entryPoint(); +}; + +#endif // INCLUDE_FIBER_H diff --git a/library/fibers/fibers.pri b/library/fibers/fibers.pri new file mode 100644 index 0000000..24fafca --- /dev/null +++ b/library/fibers/fibers.pri @@ -0,0 +1,43 @@ +INCLUDEPATH += fibers +DEPENDPATH += fibers + +HEADERS += fiber.h +SOURCES += fiber.cpp + +contains(QMAKE_CXX,g++) { + win32 { + # will fail for 64 bit win! + SOURCES += \ + switchstack_gcc_32_win.cpp \ + initializestack_32.cpp + } + + mac { + CONFIG(x86_64) { + SOURCES += \ + switchstack_gcc_64_linux_mac.s \ + initializestack_64_linux_mac.cpp + } else { + SOURCES += \ + switchstack_gcc_32_linux_mac.s \ + initializestack_32.cpp + } + } + !win32:!mac { + contains(QMAKE_CFLAGS,-m64) { + SOURCES += \ + switchstack_gcc_64_linux_mac.s \ + initializestack_64_linux_mac.cpp + } else { + SOURCES += \ + switchstack_gcc_32_linux_mac.s \ + initializestack_32.cpp + } + } +} +win32:contains(QMAKE_CXX,cl) { + # will fail for 64 bit win! + SOURCES += \ + switchstack_msvc_32.cpp \ + initializestack_32.cpp +} diff --git a/library/fibers/initializestack_32.cpp b/library/fibers/initializestack_32.cpp new file mode 100644 index 0000000..d387460 --- /dev/null +++ b/library/fibers/initializestack_32.cpp @@ -0,0 +1,21 @@ +#include <stdlib.h> + +void initializeStack(void *data, int size, void (*entry)(), void **stackPointer) +{ + void* stackBottom = (char*)data + size; + // align to 16 byte + stackBottom = (void*)((size_t)stackBottom & ~0xF); + + void **p = (void**)stackBottom; + + *(--p) = 0; // align + *(--p) = (void*)entry; // rip + *(--p) = stackBottom; // ebp + *(--p) = 0; // ebx + *(--p) = 0; // esi + *(--p) = 0; // edi + *(--p) = (void*)0x00001f80; // SIMD floating point control default + *(--p) = (void*)0x0000033f; // floating point control default + + *stackPointer = p; +} diff --git a/library/fibers/initializestack_64_linux_mac.cpp b/library/fibers/initializestack_64_linux_mac.cpp new file mode 100644 index 0000000..5465dfb --- /dev/null +++ b/library/fibers/initializestack_64_linux_mac.cpp @@ -0,0 +1,22 @@ +#include <stdlib.h> + +void initializeStack(void *data, int size, void (*entry)(), void **stackPointer) +{ + void* stackBottom = (char*)data + size; + // align to 16 byte + stackBottom = (void*)((size_t)stackBottom & ~0xF); + + void **p = (void**)stackBottom; + + *(--p) = 0; // align + *(--p) = (void*)entry; // rip + *(--p) = stackBottom; // rbp + *(--p) = 0; // rbx + *(--p) = 0; // r12 + *(--p) = 0; // r13 + *(--p) = 0; // r14 + *(--p) = 0; // r15 + *(--p) = (void*)0x00001f800000033f; // SIMD and regular floating point control defaults + + *stackPointer = p; +} diff --git a/library/fibers/initializestack_64_win.cpp b/library/fibers/initializestack_64_win.cpp new file mode 100644 index 0000000..3b29caf --- /dev/null +++ b/library/fibers/initializestack_64_win.cpp @@ -0,0 +1,44 @@ +#include <stdlib.h> + +void initializeStack(void *data, int size, void (*entry)(), void **stackPointer) +{ + void* stackBottom = (char*)data + size; + // align to 16 byte + stackBottom = (void*)((size_t)stackBottom & ~0xF); + + void **p = (void**)stackBottom; + + //*(--p) = 0; // align to 16 bytes + *(--p) = (void*)fn; // rip + *(--p) = entry; // rbp + *(--p) = 0; // rbx + *(--p) = 0; // r12 + *(--p) = 0; // r13 + *(--p) = 0; // r14 + *(--p) = 0; // r15 + *(--p) = 0; // rsi + *(--p) = 0; // rdi + *(--p) = 0; // xmm6 + *(--p) = 0; // xmm6 + *(--p) = 0; // xmm7 + *(--p) = 0; // xmm7 + *(--p) = 0; // xmm8 + *(--p) = 0; // xmm8 + *(--p) = 0; // xmm9 + *(--p) = 0; // xmm9 + *(--p) = 0; // xmm10 + *(--p) = 0; // xmm10 + *(--p) = 0; // xmm11 + *(--p) = 0; // xmm11 + *(--p) = 0; // xmm12 + *(--p) = 0; // xmm12 + *(--p) = 0; // xmm13 + *(--p) = 0; // xmm13 + *(--p) = 0; // xmm14 + *(--p) = 0; // xmm14 + *(--p) = 0; // xmm15 + *(--p) = 0; // xmm15 + *(--p) = (void*)0x00001f800000033f; // SIMD and regular floating point control defaults + + *stackPointer = p; +} diff --git a/library/fibers/switchstack_gcc_32_linux_mac.s b/library/fibers/switchstack_gcc_32_linux_mac.s new file mode 100644 index 0000000..e4eac63 --- /dev/null +++ b/library/fibers/switchstack_gcc_32_linux_mac.s @@ -0,0 +1,41 @@ +.text +.globl _switchStackInternal + +_switchStackInternal: + // save callee-saved registers + push %ebp + movl %esp, %ebp + push %ebx + push %esi + push %edi + + // store SIMD floating point control word + sub $4, %esp + stmxcsr (%esp) + + // store floating point control bytes + sub $4, %esp + fstcw (%esp) + + // save the old stack pointer + movl 0xc(%ebp), %edx + movl %esp, (%edx) + // set the new stack pointer + movl 0x8(%ebp), %esp + + // restore floating point control bytes + fnclex + fldcw (%esp) + add $4, %esp + + // restore SIMD floating point control word + ldmxcsr (%esp) + add $4, %esp + + // pop callee-saved registers + pop %edi + pop %esi + pop %ebx + pop %ebp + + ret diff --git a/library/fibers/switchstack_gcc_32_win.s b/library/fibers/switchstack_gcc_32_win.s new file mode 100644 index 0000000..2e8c140 --- /dev/null +++ b/library/fibers/switchstack_gcc_32_win.s @@ -0,0 +1,42 @@ +.global _switchStackInternal +.section .text +.def _switchStackInternal ; .scl 2 ; .type 32 ; .endef + +_switchStackInternal: + // save callee-saved registers + push %ebp + movl %esp, %ebp + push %ebx + push %esi + push %edi + + // store SIMD floating point control word + sub $4, %esp + stmxcsr (%esp) + + // store floating point control bytes + sub $4, %esp + fstcw (%esp) + + // save the old stack pointer + movl 0xc(%ebp), %edx + movl %esp, (%edx) + // set the new stack pointer + movl 0x8(%ebp), %esp + + // restore floating point control bytes + fnclex + fldcw (%esp) + add $4, %esp + + // restore SIMD floating point control word + ldmxcsr (%esp) + add $4, %esp + + // pop callee-saved registers + pop %edi + pop %esi + pop %ebx + pop %ebp + + ret diff --git a/library/fibers/switchstack_gcc_64_linux_mac.s b/library/fibers/switchstack_gcc_64_linux_mac.s new file mode 100644 index 0000000..ced6a5d --- /dev/null +++ b/library/fibers/switchstack_gcc_64_linux_mac.s @@ -0,0 +1,44 @@ +.text +.globl _switchStackInternal + +_switchStackInternal: + // save callee-saved registers + push %rbp + movq %rsp, %rbp + push %rbx + push %r12 + push %r13 + push %r14 + push %r15 + + // store SIMD floating point control word + sub $4, %rsp + stmxcsr (%rsp) + + // store floating point control bytes + sub $4, %rsp + fstcw (%rsp) + + // save the old stack pointer (second arg in rsi) + movq %rsp, (%rsi) + // set the new stack pointer (first arg in rdi) + movq %rdi, %rsp + + // restore floating point control bytes + fnclex + fldcw (%rsp) + add $4, %rsp + + // restore SIMD floating point control word + ldmxcsr (%rsp) + add $4, %rsp + + // pop callee-saved registers + pop %r15 + pop %r14 + pop %r13 + pop %r12 + pop %rbx + pop %rbp + + retq diff --git a/library/fibers/switchstack_gcc_64_win.s b/library/fibers/switchstack_gcc_64_win.s new file mode 100644 index 0000000..f75c53c --- /dev/null +++ b/library/fibers/switchstack_gcc_64_win.s @@ -0,0 +1,93 @@ +.global _switchStackInternal +.section .text +.def _switchStackInternal ; .scl 2 ; .type 32 ; .endef + +_switchStackInternal: + // save callee-saved registers + push %rbp + movq %rsp, %rbp + push %rbx + push %r12 + push %r13 + push %r14 + push %r15 + + push %rsi + push %rdi + + sub $0x10, %rsp + movupd %xmm6, (%rsp) + sub $0x10, %rsp + movupd %xmm7, (%rsp) + sub $0x10, %rsp + movupd %xmm8, (%rsp) + sub $0x10, %rsp + movupd %xmm9, (%rsp) + sub $0x10, %rsp + movupd %xmm10, (%rsp) + sub $0x10, %rsp + movupd %xmm11, (%rsp) + sub $0x10, %rsp + movupd %xmm12, (%rsp) + sub $0x10, %rsp + movupd %xmm13, (%rsp) + sub $0x10, %rsp + movupd %xmm14, (%rsp) + sub $0x10, %rsp + movupd %xmm15, (%rsp) + + // store SIMD floating point control word + sub $4, %rsp + stmxcsr (%rsp) + + // store floating point control bytes + sub $4, %rsp + fstcw (%rsp) + + // save the old stack pointer (second arg in rsi) + movq %rsp, (%rsi) + // set the new stack pointer (first arg in rdi) + movq %rdi, %rsp + + // restore floating point control bytes + fnclex + fldcw (%rsp) + add $4, %rsp + + // restore SIMD floating point control word + ldmxcsr (%rsp) + add $4, %rsp + + // pop callee-saved registers + movupd (%rsp), %xmm15 + add $0x10, %rsp + movupd (%rsp), %xmm14 + add $0x10, %rsp + movupd (%rsp), %xmm13 + add $0x10, %rsp + movupd (%rsp), %xmm12 + add $0x10, %rsp + movupd (%rsp), %xmm11 + add $0x10, %rsp + movupd (%rsp), %xmm10 + add $0x10, %rsp + movupd (%rsp), %xmm9 + add $0x10, %rsp + movupd (%rsp), %xmm8 + add $0x10, %rsp + movupd (%rsp), %xmm7 + add $0x10, %rsp + movupd (%rsp), %xmm6 + add $0x10, %rsp + + pop %rdi + pop %rsi + + pop %r15 + pop %r14 + pop %r13 + pop %r12 + pop %rbx + pop %rbp + + retq diff --git a/library/fibers/switchstack_msvc_32.cpp b/library/fibers/switchstack_msvc_32.cpp new file mode 100644 index 0000000..eae68ee --- /dev/null +++ b/library/fibers/switchstack_msvc_32.cpp @@ -0,0 +1,44 @@ +extern "C" { +void __declspec(naked) _switchStackInternal(void* to, void** from) +{ + __asm { + // save callee-saved registers + PUSH EBP + MOV EBP, ESP + PUSH EBX + PUSH ESI + PUSH EDI + + // store SIMD floating point control word + SUB ESP, 4 + STMXCSR [ESP] + + // store floating point control bytes + SUB ESP, 4 + FSTCW [ESP] + + // save the old stack pointer + MOV EDX, dword ptr 12[EBP] + MOV [EDX], ESP + // set the new stack pointer + MOV ESP, dword ptr 8[EBP] + + // restore floating point control bytes + FNCLEX + FLDCW [ESP] + ADD ESP, 4 + + // restore SIMD floating point control word + LDMXCSR [ESP] + ADD ESP, 4 + + // pop callee-saved registers + POP EDI + POP ESI + POP EBX + POP EBP + + RET + } +} +} diff --git a/library/fibers/switchstack_msvc_64.asm b/library/fibers/switchstack_msvc_64.asm new file mode 100644 index 0000000..3c6f321 --- /dev/null +++ b/library/fibers/switchstack_msvc_64.asm @@ -0,0 +1,98 @@ +.model flat, c +.code + +_switchStackInternal PROC to:QWORD, from:QWORD + ; save callee-saved registers + PUSH RBP + MOV RBP, RSP + PUSH RBX + PUSH ESI + PUSH EDI + + PUSH R12 + PUSH R13 + PUSH R14 + PUSH R15 + + PUSH RSI + PUSH RDI + + SUB RSP, 0x10 + MOVUPD [RSP], XMM6 + SUB RSP, 0x10 + MOVUPD [RSP], XMM7 + SUB RSP, 0x10 + MOVUPD [RSP], XMM8 + SUB RSP, 0x10 + MOVUPD [RSP], XMM9 + SUB RSP, 0x10 + MOVUPD [RSP], XMM10 + SUB RSP, 0x10 + MOVUPD [RSP], XMM11 + SUB RSP, 0x10 + MOVUPD [RSP], XMM12 + SUB RSP, 0x10 + MOVUPD [RSP], XMM13 + SUB RSP, 0x10 + MOVUPD [RSP], XMM14 + SUB RSP, 0x10 + MOVUPD [RSP], XMM15 + + ; store SIMD floating point control word + SUB RSP, 4 + STMXCSR [RSP] + + ; store floating point control bytes + SUB RSP, 4 + FSTCW [RSP] + + ; save the old stack pointer + MOV [from], RSP + ; set the new stack pointer + MOV RSP, to + + ; restore floating point control bytes + FNCLEX + FLDCW [RSP] + ADD RSP, 4 + + ; restore SIMD floating point control word + LDMXCSR [RSP] + ADD RSP, 4 + + ; pop callee-saved registers + MOVUPD XMM15, [RSP] + ADD RSP, 0x10 + MOVUPD XMM14, [RSP] + ADD RSP, 0x10 + MOVUPD XMM13, [RSP] + ADD RSP, 0x10 + MOVUPD XMM12, [RSP] + ADD RSP, 0x10 + MOVUPD XMM11, [RSP] + ADD RSP, 0x10 + MOVUPD XMM10, [RSP] + ADD RSP, 0x10 + MOVUPD XMM9, [RSP] + ADD RSP, 0x10 + MOVUPD XMM8, [RSP] + ADD RSP, 0x10 + MOVUPD XMM7, [RSP] + ADD RSP, 0x10 + MOVUPD XMM6, [RSP] + ADD RSP, 0x10 + + POP RDI + POP RSI + + POP R15 + POP R14 + POP R13 + POP R12 + POP RBX + POP RBP + + RET +_switchStackInternal ENDP + +end diff --git a/library/remotecontrolwidget.pro b/library/remotecontrolwidget.pro index 3244dfe..c99b487 100644 --- a/library/remotecontrolwidget.pro +++ b/library/remotecontrolwidget.pro @@ -15,6 +15,7 @@ SOURCES += \ remotecontrolwidget.cpp \ toolbox.cpp \ optionsitem.cpp \ + scriptadapter.cpp \ style/stylehelper.cpp \ style/styledbar.cpp \ style/styleanimator.cpp \ @@ -23,13 +24,15 @@ SOURCES += \ style/fancylineedit.cpp \ components/locationui.cpp \ components/powerbutton.cpp \ - components/batterybutton.cpp + components/batterybutton.cpp \ + components/scriptui.cpp HEADERS += \ remotecontrolwidget.h \ remotecontrolwidget_global.h \ toolbox.h \ optionsitem.h \ + scriptadapter.h \ style/styledbar.h \ style/styleanimator.h \ style/qtcassert.h \ @@ -39,8 +42,11 @@ HEADERS += \ style/stylehelper.h \ components/locationui.h \ components/powerbutton.h \ - components/batterybutton.h + components/batterybutton.h \ + components/scriptui.h RESOURCES += \ style/style.qrc \ components/component.qrc + +include(fibers/fibers.pri) diff --git a/library/scriptadapter.cpp b/library/scriptadapter.cpp new file mode 100644 index 0000000..dabf103 --- /dev/null +++ b/library/scriptadapter.cpp @@ -0,0 +1,214 @@ +#include "scriptadapter.h" + +#include "optionsitem.h" + +#include <QtCore/QMetaEnum> +#include <QtCore/QCoreApplication> +#include <QtCore/QTimer> +#include <QtCore/QTime> +#include <QtCore/QFile> +#include <QtCore/QDir> +#include <QtCore/QDebug> +#include <QtGui/QMessageBox> + +template <typename Enum> +static void scriptToEnum(const QScriptValue &obj, Enum &e) +{ + e = static_cast<Enum>(obj.toInt32()); +} + +template <typename Enum> +static QScriptValue enumToScript(QScriptEngine *engine, const Enum &e) +{ + QScriptValue obj = engine->newVariant(e); + return obj; +} + +// Adds a property 'name' to 'to' that contains all the values of the enum 'name', +// which must be registered in 'mo'. Also registers the type 'Enum' to the QScriptEngine. +template <typename Enum> +static void exposeEnum(QScriptEngine *engine, QScriptValue *to, const char *name, const QMetaObject *mo) +{ + qScriptRegisterMetaType<Enum>(engine, &enumToScript<Enum>, &scriptToEnum<Enum>); + QScriptValue enumvals = engine->newObject(); + int enumIndex = mo->indexOfEnumerator(name); + Q_ASSERT(enumIndex != -1 && "enum not found in meta object"); + QMetaEnum metaEnum = mo->enumerator(enumIndex); + for (int i = 0; i < metaEnum.keyCount(); ++i) + enumvals.setProperty(metaEnum.key(i), metaEnum.value(i)); + to->setProperty(name, enumvals); +} + +ScriptAdapter::ScriptAdapter(QObject *parent) + : QObject(parent) +{ + mScriptRunner = new QTimer(this); + mScriptRunner->setSingleShot(true); + mScriptRunner->setInterval(100); + connect(mScriptRunner, SIGNAL(timeout()), this, SLOT(continueScripts())); + mScriptRunner->start(); + + mMessageBox = new QMessageBox; +} + +ScriptAdapter::~ScriptAdapter() +{ + delete mMessageBox; +} + +void ScriptAdapter::addScriptInterface(const QString &name, QObject *interface) +{ + mScriptInterfaces.insert(name, interface); +} + +ScriptFiber *ScriptAdapter::script(int index) +{ + if (index >= 0 && index < mActiveScripts.length()) + return mActiveScripts[index]; + else + return 0; +} + +void ScriptAdapter::runAutostartScripts() +{ + QDir dir(QCoreApplication::applicationDirPath()); + dir.cd(QLatin1String("scripts")); + dir.cd(QLatin1String("autostart")); + + QStringList scriptPatterns; + scriptPatterns << "*.js" << "*.qs"; + foreach (const QFileInfo &file, dir.entryInfoList(scriptPatterns, QDir::Files)) { + run(file.filePath()); + } +} + +ScriptFiber *ScriptAdapter::run(const QString &filePath) +{ + QFile file(filePath); + if (!file.open(QIODevice::ReadOnly)) { + qWarning() << "Could not read script file: " << filePath; + return 0; + } + + QFileInfo fileInfo(file); + ScriptFiber *script = new ScriptFiber(fileInfo.baseName(), file.readAll(), this); + mActiveScripts += script; + + emit scriptStart(script); + + return script; +} + +void ScriptAdapter::continueScripts() { + for (int i = 0; i < mActiveScripts.length(); ++i) { + ScriptFiber *script = mActiveScripts[i]; + + if (script->status() == Fiber::Terminated) { + emit scriptStop(i); + + mActiveScripts.removeOne(script); + delete script; + break; + } + + if (!script->isPaused() || script->isTerminating()) { + script->cont(); + } + } + + mScriptRunner->start(); +} + +void ScriptAdapter::yield(int ms) +{ + ScriptFiber *scriptFiber = dynamic_cast<ScriptFiber *>(Fiber::currentFiber()); + if (!scriptFiber) + return; + + QTime timer; + timer.start(); + while (timer.elapsed() < ms && !scriptFiber->isTerminating()) { + scriptFiber->suspend(); + } +} + +void ScriptAdapter::messageBox(const QString &text) +{ + mMessageBox->setText(text); + + // message boxes should block + ScriptFiber *fiber = dynamic_cast<ScriptFiber *>(Fiber::currentFiber()); + fiber->beginBlocking(); + mMessageBox->exec(); + fiber->endBlocking(); +} + +ScriptFiber::ScriptFiber(const QString &name, const QString &scriptCode, + ScriptAdapter *adapter) + : Fiber(524228) // FIXME: 131072 was not big enough... + , mName(name) + , mScriptCode(scriptCode) + , mAdapter(adapter) + , mPaused(false) + , mTerminate(false) +{ + mStopTimer.setInterval(10); + connect(&mStopTimer, SIGNAL(timeout()), this, SLOT(suspend())); + + // trigger event processing during script evaluation + mEngine.setProcessEventsInterval(10); + + // inject the ScriptAdapters functions into the global scope + QScriptValue adapterValue = mEngine.newQObject(adapter); + adapterValue.setPrototype(mEngine.globalObject()); + mEngine.setGlobalObject(adapterValue); + + QHashIterator<QString, QObject *> iter(mAdapter->mScriptInterfaces); + while (iter.hasNext()) { + iter.next(); + QScriptValue interface = mEngine.newQObject(iter.value()); + mEngine.globalObject().setProperty(iter.key(), interface); + } +} + +void ScriptFiber::run() +{ + mStopTimer.start(); + QScriptValue value = mEngine.evaluate(mScriptCode); + if (value.isError()) { + qWarning() << "Script execution resulted in an error:" << value.toString(); + } + mStopTimer.stop(); +} + +void ScriptFiber::requestTerminate() +{ + mTerminate = true; +} + +void ScriptFiber::togglePause() +{ + mPaused = !mPaused; +} + +void ScriptFiber::suspend() +{ + if (Fiber::currentFiber() == this) { + Fiber::yield(); + + if (mTerminate) { + mEngine.abortEvaluation(mEngine.currentContext()->throwError("Aborted")); + mStopTimer.stop(); + } + } +} + +void ScriptFiber::beginBlocking() +{ + mStopTimer.stop(); +} + +void ScriptFiber::endBlocking() +{ + mStopTimer.start(); +} diff --git a/library/scriptadapter.h b/library/scriptadapter.h new file mode 100644 index 0000000..d17525e --- /dev/null +++ b/library/scriptadapter.h @@ -0,0 +1,90 @@ +#ifndef SCRIPTADAPTER_H +#define SCRIPTADAPTER_H + +#include "remotecontrolwidget_global.h" +#include "fibers/fiber.h" + +#include <QtCore/QObject> +#include <QtCore/QTimer> +#include <QtCore/QStringList> +#include <QtCore/QHash> +#include <QtScript/QScriptEngine> + +class ScriptFiber; +class QMessageBox; + +class REMOTECONTROLWIDGETSHARED_EXPORT ScriptAdapter : public QObject +{ + Q_OBJECT +public: + explicit ScriptAdapter(QObject *parent = 0); + virtual ~ScriptAdapter(); + + Q_INVOKABLE void yield(int ms); + Q_INVOKABLE void messageBox(const QString &text); + + void runAutostartScripts(); + ScriptFiber *script(int index); + + void addScriptInterface(const QString &name, QObject *interface); + +public slots: + ScriptFiber *run(const QString &filePath); + +signals: + void scriptStart(ScriptFiber *script) const; + void scriptStop(int index) const; + +private slots: + void continueScripts(); + +private: + QHash<QString, QObject *> mScriptInterfaces; + QList<ScriptFiber *> mActiveScripts; + QTimer *mScriptRunner; + + QMessageBox *mMessageBox; + + friend class ScriptFiber; +}; + +class ScriptFiber : public QObject, public Fiber +{ + Q_OBJECT +public: + ScriptFiber(const QString &name, const QString &scriptCode, + ScriptAdapter *adapter); + + QString name() const + { return mName; } + + void requestTerminate(); + bool isTerminating() const + { return mTerminate; } + + void togglePause(); + bool isPaused() const + { return mPaused; } + + void beginBlocking(); + void endBlocking(); + +public slots: + void suspend(); + +protected: + virtual void run(); + +private: + QString mName; + QString mScriptCode; + ScriptAdapter *mAdapter; + + bool mPaused; + bool mTerminate; + + QScriptEngine mEngine; + QTimer mStopTimer; +}; + +#endif // SCRIPTADAPTER_H |