diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..40301363 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/build-doric-*/ \ No newline at end of file diff --git a/doric/.gitignore b/doric/.gitignore new file mode 100644 index 00000000..fab7372d --- /dev/null +++ b/doric/.gitignore @@ -0,0 +1,73 @@ +# This file is used to ignore files which are generated +# ---------------------------------------------------------------------------- + +*~ +*.autosave +*.a +*.core +*.moc +*.o +*.obj +*.orig +*.rej +*.so +*.so.* +*_pch.h.cpp +*_resource.rc +*.qm +.#* +*.*# +core +!core/ +tags +.DS_Store +.directory +*.debug +Makefile* +*.prl +*.app +moc_*.cpp +ui_*.h +qrc_*.cpp +Thumbs.db +*.res +*.rc +/.qmake.cache +/.qmake.stash + +# qtcreator generated files +*.pro.user* + +# xemacs temporary files +*.flc + +# Vim temporary files +.*.swp + +# Visual Studio generated files +*.ib_pdb_index +*.idb +*.ilk +*.pdb +*.sln +*.suo +*.vcproj +*vcproj.*.*.user +*.ncb +*.sdf +*.opensdf +*.vcxproj +*vcxproj.* + +# MinGW generated files +*.Debug +*.Release + +# Python byte code +*.pyc + +# Binaries +# -------- +*.dll +*.exe + diff --git a/doric/constant.cpp b/doric/constant.cpp new file mode 100644 index 00000000..e22a425a --- /dev/null +++ b/doric/constant.cpp @@ -0,0 +1,32 @@ +#include "constant.h" + +const QString Constant::INJECT_LOG = "nativeLog"; +const QString Constant::INJECT_REQUIRE = "nativeRequire"; +const QString Constant::INJECT_TIMER_SET = "nativeSetTimer"; +const QString Constant::INJECT_TIMER_CLEAR = "nativeClearTimer"; +const QString Constant::INJECT_BRIDGE = "nativeBridge"; +const QString Constant::INJECT_EMPTY = "nativeEmpty"; + +const QString Constant::TEMPLATE_CONTEXT_CREATE = QString("Reflect.apply(") + + QString("function(doric,context,Entry,require,exports){") + QString("\n") + + QString("%s1") + QString("\n") + + QString("},doric.jsObtainContext(\"%s2\"),[") + + QString("undefined,") + + QString("doric.jsObtainContext(\"%s3\"),") + + QString("doric.jsObtainEntry(\"%s4\"),") + + QString("doric.__require__") + + QString(",{}") + + QString("])"); +const QString Constant::TEMPLATE_MODULE = QString("Reflect.apply(doric.jsRegisterModule,this,[") + + QString("\"%s1\",") + + QString("Reflect.apply(function(__module){") + + QString("(function(module,exports,require){") + QString("\n") + + QString("%s2") + QString("\n") + + QString("})(__module,__module.exports,doric.__require__);") + + QString("\nreturn __module.exports;") + + QString("},this,[{exports:{}}])") + + QString("])"); +const QString Constant::TEMPLATE_CONTEXT_DESTROY = QString("doric.jsReleaseContext(\"%s\")"); + +const QString Constant::GLOBAL_DORIC = QString("doric"); +const QString Constant::DORIC_TIMER_CALLBACK = QString("jsCallbackTimer"); diff --git a/doric/constant.h b/doric/constant.h new file mode 100644 index 00000000..a0d27026 --- /dev/null +++ b/doric/constant.h @@ -0,0 +1,24 @@ +#ifndef CONSTANT_H +#define CONSTANT_H + +#include + +class Constant { + +public: + static const QString INJECT_LOG; + static const QString INJECT_REQUIRE; + static const QString INJECT_TIMER_SET; + static const QString INJECT_TIMER_CLEAR; + static const QString INJECT_BRIDGE; + static const QString INJECT_EMPTY; + + static const QString TEMPLATE_CONTEXT_CREATE; + static const QString TEMPLATE_MODULE; + static const QString TEMPLATE_CONTEXT_DESTROY; + + static const QString GLOBAL_DORIC; + static const QString DORIC_TIMER_CALLBACK; +}; + +#endif // CONSTANT_H diff --git a/doric/context.h b/doric/context.h new file mode 100644 index 00000000..a2b15369 --- /dev/null +++ b/doric/context.h @@ -0,0 +1,24 @@ +#ifndef CONTEXT_H +#define CONTEXT_H + +#include +#include "driver/driver.h" +#include "driver/native_driver.h" + +class Context +{ + +private: + int contextId; + QString* source; + +public: + Driver* driver = NativeDriver::getInstance(); + + Context(int contextId, QString* source) { + this->contextId = contextId; + this->source = source; + } +}; + +#endif // CONTEXT_H diff --git a/doric/context_manager.h b/doric/context_manager.h new file mode 100644 index 00000000..5597545d --- /dev/null +++ b/doric/context_manager.h @@ -0,0 +1,41 @@ +#ifndef CONTEXT_MANAGER_H +#define CONTEXT_MANAGER_H + +#include +#include +#include + +#include "context.h" + +class ContextManager +{ +private: + + static ContextManager *local_instance; + ContextManager() { + qDebug() << "ContextManager constructor"; + } + + ~ContextManager() { + qDebug() << "ContextManager destructor"; + } + + QAtomicInt *counter = new QAtomicInt(); + QMap *contextMap = new QMap(); + +public: + static ContextManager *getInstance() { + static ContextManager locla_s; + return &locla_s; + } + + Context* createContext(QString* script, QString* source) { + int contextId = counter->fetchAndAddOrdered(1); + Context* context = new Context(contextId, source); + contextMap->insert(contextId, context); + context->driver->createContext(contextId, script); + return context; + } +}; + +#endif // CONTEXT_MANAGER_H diff --git a/doric/doric.pro b/doric/doric.pro new file mode 100644 index 00000000..69c073fa --- /dev/null +++ b/doric/doric.pro @@ -0,0 +1,45 @@ +QT += quick + +CONFIG += c++11 + +# The following define makes your compiler emit warnings if you use +# any Qt feature that has been marked deprecated (the exact warnings +# depend on your compiler). Refer to the documentation for the +# deprecated API to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if it uses deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + constant.cpp \ + driver/native_driver.cpp \ + main.cpp + +RESOURCES += qml.qrc + +# Additional import path used to resolve QML modules in Qt Creator's code model +QML_IMPORT_PATH = + +# Additional import path used to resolve QML modules just for Qt Quick Designer +QML_DESIGNER_IMPORT_PATH = + +# Default rules for deployment. +qnx: target.path = /tmp/$${TARGET}/bin +else: unix:!android: target.path = /opt/$${TARGET}/bin +!isEmpty(target.path): INSTALLS += target + +HEADERS += \ + constant.h \ + context.h \ + context_manager.h \ + driver/driver.h \ + driver/native_driver.h \ + engine/js_engine.h \ + native/native_bridge.h \ + native/native_empty.h \ + native/native_log.h \ + native/native_timer.h \ + template/singleton.h diff --git a/doric/driver/driver.h b/doric/driver/driver.h new file mode 100644 index 00000000..23923616 --- /dev/null +++ b/doric/driver/driver.h @@ -0,0 +1,19 @@ +#ifndef DRIVER_H +#define DRIVER_H + +#include + +class Driver { + +public: + virtual void createContext(int contextId, QString* script) = 0; + virtual void destroyContext(int contextId) = 0; + + virtual ~Driver() = default; +}; + +#define DriverInterface "pub.doric.DriverInterface" + +Q_DECLARE_INTERFACE(Driver, DriverInterface) + +#endif // DRIVER_H diff --git a/doric/driver/native_driver.cpp b/doric/driver/native_driver.cpp new file mode 100644 index 00000000..295c6d7f --- /dev/null +++ b/doric/driver/native_driver.cpp @@ -0,0 +1,13 @@ +#include "native_driver.h" + +NativeDriver::~NativeDriver() { + qDebug() << "NativeDriver destructor"; +} + +void NativeDriver::createContext(int contextId, QString *script) { + jsEngine->prepareContext(contextId, script); +} + +void NativeDriver::destroyContext(int contextId) { + jsEngine->destroyContext(contextId); +} diff --git a/doric/driver/native_driver.h b/doric/driver/native_driver.h new file mode 100644 index 00000000..cac8528c --- /dev/null +++ b/doric/driver/native_driver.h @@ -0,0 +1,33 @@ +#ifndef NATIVE_DRIVER_H +#define NATIVE_DRIVER_H + +#include + +#include "driver.h" +#include "engine/js_engine.h" + +class NativeDriver : public Driver { + Q_INTERFACES(Driver) + +private: + static NativeDriver *local_instance; + NativeDriver() { + qDebug() << "NativeDriver constructor"; + } + + ~NativeDriver() override; + + JSEngine *jsEngine = new JSEngine(); + +public: + static NativeDriver *getInstance() { + static NativeDriver locla_s; + return &locla_s; + } + + void createContext(int contextId, QString *script) override; + + void destroyContext(int contextId) override; +}; + +#endif // NATIVE_DRIVER_H diff --git a/doric/engine/js_engine.h b/doric/engine/js_engine.h new file mode 100644 index 00000000..e415ebb3 --- /dev/null +++ b/doric/engine/js_engine.h @@ -0,0 +1,91 @@ +#ifndef JS_ENGINE_H +#define JS_ENGINE_H + +#include +#include +#include + +#include "constant.h" +#include "native/native_empty.h" +#include "native/native_log.h" +#include "native/native_timer.h" + +class JSEngine : public QObject { + Q_OBJECT + +public: + QJSEngine *engine; + JSEngine(QObject *parent = nullptr) : QObject(parent) { + initJSEngine(); + injectGlobal(); + initDoricRuntime(); + } + + void prepareContext(int contextId, QString* script) { + QString contextIdString = QString::number(contextId); + QString source = QString(Constant::TEMPLATE_CONTEXT_CREATE) + .replace("%s1", *script) + .replace("%s2", contextIdString) + .replace("%s3", contextIdString) + .replace("%s4", contextIdString); + QJSValue result = engine->evaluate(source, "context://" + contextIdString); + qDebug() << "context://" + contextIdString + " result: " + result.toString(); + } + + void destroyContext(int contextId) { + QString contextIdString = QString::number(contextId); + QString source = QString(Constant::TEMPLATE_CONTEXT_DESTROY) + .replace("%s", contextIdString); + QJSValue result = engine->evaluate(source, "_context://" + contextIdString); + qDebug() << "context://" + contextIdString + " result: " + result.toString(); + } + +private: + void initJSEngine() { + engine = new QJSEngine(); + engine->installExtensions(QJSEngine::AllExtensions); + } + + void injectGlobal() { + QJSValue nativeLog = engine->newQObject(new NativeLog()); + engine->globalObject().setProperty(Constant::INJECT_LOG, nativeLog.property("function")); + + QJSValue nativeTimer = engine->newQObject(new NativeTimer(engine)); + engine->globalObject().setProperty(Constant::INJECT_TIMER_SET, nativeTimer.property("setTimer")); + engine->globalObject().setProperty(Constant::INJECT_TIMER_CLEAR, nativeTimer.property("clearTimer")); + + QJSValue nativeEmpty = engine->newQObject(new NativeEmpty()); + engine->globalObject().setProperty(Constant::INJECT_EMPTY, nativeEmpty.property("function")); + } + + void initDoricRuntime() { + { + QFile *file = new QFile("/Users/maverick/Workspace/doric/js-framework/bundle/doric-sandbox.js"); + file->open(QFile::ReadOnly | QFile::Text); + QTextStream in(file); + QString script = in.readAll(); + file->close(); + delete file; + + QJSValue result = engine->evaluate(script, "doric-sandbox.js"); + qDebug() << "doric-sandbox.js result: " + result.toString(); + } + + { + QFile *file = new QFile("/Users/maverick/Workspace/doric/js-framework/bundle/doric-lib.js"); + file->open(QFile::ReadOnly | QFile::Text); + QTextStream in(file); + QString script = in.readAll(); + file->close(); + delete file; + + QString lib = QString(Constant::TEMPLATE_MODULE) + .replace("%s1", "doric") + .replace("%s2", script); + QJSValue result = engine->evaluate(lib, "doric-lib.js"); + qDebug() << "doric-lib.js result: " + result.toString(); + } + } +}; + +#endif // JS_ENGINE_H diff --git a/doric/main.cpp b/doric/main.cpp new file mode 100644 index 00000000..b45d3fc1 --- /dev/null +++ b/doric/main.cpp @@ -0,0 +1,33 @@ +#include +#include +#include +#include + +#include "context_manager.h" + +int main(int argc, char *argv[]) +{ + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + + QGuiApplication app(argc, argv); + + QQmlApplicationEngine engine; + const QUrl url(QStringLiteral("qrc:/main.qml")); + QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, + &app, [url](QObject *obj, const QUrl &objUrl) { + if (!obj && url == objUrl) + QCoreApplication::exit(-1); + }, Qt::QueuedConnection); + engine.load(url); + + QFile* file = new QFile("/Users/maverick/Workspace/doric/demo/bundle/src/Snake.js"); + file->open(QFile::ReadOnly | QFile::Text); + QTextStream in(file); + QString script = in.readAll(); + file->close(); + delete file; + + QString* source = new QString("Snake.js"); + ContextManager::getInstance()->createContext(&script, source); + return app.exec(); +} diff --git a/doric/main.qml b/doric/main.qml new file mode 100644 index 00000000..45ee20a2 --- /dev/null +++ b/doric/main.qml @@ -0,0 +1,9 @@ +import QtQuick 2.6 +import QtQuick.Window 2.2 + +Window { + visible: true + width: 640 + height: 480 + title: qsTr("Hello World") +} diff --git a/doric/native/native_bridge.h b/doric/native/native_bridge.h new file mode 100644 index 00000000..6dd83041 --- /dev/null +++ b/doric/native/native_bridge.h @@ -0,0 +1,18 @@ +#ifndef NATIVE_BRIDGE_H +#define NATIVE_BRIDGE_H + +#include +#include + +class NativeBridge : public QObject { + Q_OBJECT + +public: + NativeBridge(QObject *parent = nullptr) : QObject(parent) {} + + Q_INVOKABLE void function() { + + } +}; + +#endif // NATIVE_BRIDGE_H diff --git a/doric/native/native_empty.h b/doric/native/native_empty.h new file mode 100644 index 00000000..14340137 --- /dev/null +++ b/doric/native/native_empty.h @@ -0,0 +1,18 @@ +#ifndef NATIVE_EMPTY_H +#define NATIVE_EMPTY_H + +#include +#include + +class NativeEmpty : public QObject { + Q_OBJECT + +public: + NativeEmpty(QObject *parent = nullptr) : QObject(parent) {} + + Q_INVOKABLE void function() { + qDebug() << "nativeEmpty"; + } +}; + +#endif // NATIVE_EMPTY_H diff --git a/doric/native/native_log.h b/doric/native/native_log.h new file mode 100644 index 00000000..f2caf7f1 --- /dev/null +++ b/doric/native/native_log.h @@ -0,0 +1,24 @@ +#ifndef NATIVELOG_H +#define NATIVELOG_H + +#include +#include + +class NativeLog : public QObject { + Q_OBJECT + +public: + NativeLog(QObject *parent = nullptr) : QObject(parent) {} + + Q_INVOKABLE void function(QString level, QString content) { + if (level == 'w') { + qWarning() << content; + } else if (level == 'd') { + qDebug() << content; + } else if (level == 'e') { + qCritical() << content; + } + } +}; + +#endif // NATIVELOG_H diff --git a/doric/native/native_timer.h b/doric/native/native_timer.h new file mode 100644 index 00000000..b85ba9e7 --- /dev/null +++ b/doric/native/native_timer.h @@ -0,0 +1,51 @@ +#ifndef NATIVE_TIMER_H +#define NATIVE_TIMER_H + +#include +#include +#include +#include + +#include "constant.h" + +class NativeTimer : public QObject { + Q_OBJECT + +private: + QSet *deletedTimerIds = new QSet(); + QJSEngine* engine; + +public: + NativeTimer(QJSEngine* engine, QObject *parent = nullptr) : QObject(parent) { + this->engine = engine; + } + + Q_INVOKABLE void setTimer(long timerId, int time, bool repeat) { + QTimer* timer = new QTimer(this); + timer->setSingleShot(!repeat); + connect(timer, &QTimer::timeout, this, [=] () { + if (deletedTimerIds->contains(timerId)) { + deletedTimerIds->remove(timerId); + delete timer; + } else { + engine->evaluate( + Constant::GLOBAL_DORIC + "." + + Constant::DORIC_TIMER_CALLBACK + "(" + + QString::number(timerId) + ")" + ); + + if (!repeat) { + deletedTimerIds->remove(timerId); + delete timer; + } + } + }); + timer->start(time); + } + + Q_INVOKABLE void clearTimer(long timerId) { + deletedTimerIds->insert(timerId); + } + +}; +#endif // NATIVE_TIMER_H diff --git a/doric/qml.qrc b/doric/qml.qrc new file mode 100644 index 00000000..5f6483ac --- /dev/null +++ b/doric/qml.qrc @@ -0,0 +1,5 @@ + + + main.qml + + diff --git a/doric/template/singleton.h b/doric/template/singleton.h new file mode 100644 index 00000000..0bee72fb --- /dev/null +++ b/doric/template/singleton.h @@ -0,0 +1,25 @@ +#ifndef SINGLETON_H +#define SINGLETON_H + +#include + +class Singleton +{ +private: + static Singleton *local_instance; + Singleton() { + qDebug() << "constructor"; + } + + ~Singleton() { + qDebug() << "destructor"; + } + +public: + static Singleton *getInstance() { + static Singleton locla_s; + return &locla_s; + } +}; + +#endif // SINGLETON_H