feat: print 多参数和 promise 支持;修复终端主题设置;
This commit is contained in:
parent
098f08e906
commit
3f26766575
|
@ -210,7 +210,9 @@ set(CONTROL_SRC
|
|||
src/control/gotolinewidget.h
|
||||
src/control/gotolinewidget.cpp
|
||||
src/control/codeeditcontrolwidget.h
|
||||
src/control/codeeditcontrolwidget.cpp)
|
||||
src/control/codeeditcontrolwidget.cpp
|
||||
src/control/scrollablelabel.h
|
||||
src/control/scrollablelabel.cpp)
|
||||
|
||||
set(CLASS_SRC
|
||||
src/class/logger.cpp
|
||||
|
@ -356,7 +358,8 @@ set(SCRIPT_ADDON_SRC
|
|||
src/scriptaddon/scriptjson.h
|
||||
src/scriptaddon/scriptjson.cpp
|
||||
src/scriptaddon/scriptfile.cpp
|
||||
src/scriptaddon/scriptfile.h)
|
||||
src/scriptaddon/scriptfile.h
|
||||
src/scriptaddon/aspromise.hpp)
|
||||
|
||||
# localization support
|
||||
file(
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -18,6 +18,7 @@
|
|||
#include "aspreprocesser.h"
|
||||
#include "class/qascodeparser.h"
|
||||
#include "class/scriptmachine.h"
|
||||
#include "scriptaddon/aspromise.hpp"
|
||||
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
|
@ -28,7 +29,8 @@ Q_GLOBAL_STATIC_WITH_ARGS(
|
|||
({"__AS_ARRAY__", "__AS_ANY__", "__AS_GRID__", "__AS_HANDLE__",
|
||||
"__AS_MATH__", "__AS_WEAKREF__", "__AS_COROUTINE__", "__WING_FILE__",
|
||||
"__WING_STRING__", "__WING_COLOR__", "__WING_JSON__", "__WING_REGEX__",
|
||||
"__WING_DICTIONARY__"}));
|
||||
"__WING_DICTIONARY__", "__WING_PRINT_VAR__", "__WING_PRINT_LN__",
|
||||
"__AS_PROMISE__"}));
|
||||
|
||||
AsPreprocesser::AsPreprocesser(asIScriptEngine *engine) : engine(engine) {
|
||||
Q_ASSERT(engine);
|
||||
|
@ -656,6 +658,12 @@ int AsPreprocesser::processScriptSection(const QByteArray &script,
|
|||
}
|
||||
}
|
||||
|
||||
// asPromise co_await keyword expansion
|
||||
size_t len = modifiedScript.size();
|
||||
auto data = AsGeneratePromiseEntrypoints(modifiedScript.data(), &len);
|
||||
modifiedScript = QByteArray(data, len);
|
||||
asFreeMem(data);
|
||||
|
||||
// Build the actual script
|
||||
engine->SetEngineProperty(asEP_COPY_SCRIPT_SECTIONS, true);
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
#include "class/qascodeparser.h"
|
||||
#include "class/settingmanager.h"
|
||||
#include "define.h"
|
||||
#include "scriptaddon/aspromise.hpp"
|
||||
#include "scriptaddon/scriptcolor.h"
|
||||
#include "scriptaddon/scriptfile.h"
|
||||
#include "scriptaddon/scriptjson.h"
|
||||
|
@ -142,6 +143,12 @@ bool ScriptMachine::isRunning(ConsoleMode mode) const {
|
|||
return _ctx.value(mode) != nullptr;
|
||||
}
|
||||
|
||||
#define INS_1 "const ?&in = null"
|
||||
#define INS_2 INS_1 ", " INS_1
|
||||
#define INS_4 INS_2 ", " INS_2
|
||||
#define INS_8 INS_4 ", " INS_4
|
||||
#define INS_16 INS_8 ", " INS_8
|
||||
|
||||
bool ScriptMachine::configureEngine() {
|
||||
if (_engine == nullptr) {
|
||||
return false;
|
||||
|
@ -211,9 +218,17 @@ bool ScriptMachine::configureEngine() {
|
|||
q_check_ptr(_engine->GetTypeInfoByName("color"));
|
||||
|
||||
// Register a couple of extra functions for the scripts
|
||||
r = _engine->RegisterGlobalFunction("void print(? &in obj)",
|
||||
asMETHOD(ScriptMachine, print),
|
||||
asCALL_THISCALL_ASGLOBAL, this);
|
||||
r = _engine->RegisterGlobalFunction(
|
||||
"void print(const ? &in obj, const ? &in = null," INS_16 ")",
|
||||
asFUNCTION(print), asCALL_GENERIC);
|
||||
Q_ASSERT(r >= 0);
|
||||
if (r < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
r = _engine->RegisterGlobalFunction(
|
||||
"void println(const ? &in obj, const ? &in = null," INS_16 ")",
|
||||
asFUNCTION(println), asCALL_GENERIC);
|
||||
Q_ASSERT(r >= 0);
|
||||
if (r < 0) {
|
||||
return false;
|
||||
|
@ -341,18 +356,56 @@ void ScriptMachine::exceptionCallback(asIScriptContext *context) {
|
|||
}
|
||||
}
|
||||
|
||||
void ScriptMachine::print(void *ref, int typeId) {
|
||||
void ScriptMachine::print(asIScriptGeneric *args) {
|
||||
auto context = asGetActiveContext();
|
||||
if (context) {
|
||||
ConsoleMode mode = ConsoleMode(reinterpret_cast<asPWORD>(
|
||||
context->GetUserData(AsUserDataType::UserData_ContextMode)));
|
||||
|
||||
auto &m = ScriptMachine::instance();
|
||||
|
||||
MessageInfo info;
|
||||
info.mode = mode;
|
||||
info.type = MessageType::Print;
|
||||
info.message = _debugger->toString(ref, typeId, _engine);
|
||||
|
||||
outputMessage(info);
|
||||
for (int i = 0; i < args->GetArgCount(); ++i) {
|
||||
void *ref = args->GetArgAddress(i);
|
||||
int typeId = args->GetArgTypeId(i);
|
||||
|
||||
if (typeId) {
|
||||
info.message.append(
|
||||
m.debugger()->toString(ref, typeId, m.engine()));
|
||||
}
|
||||
}
|
||||
|
||||
m.outputMessage(info);
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptMachine::println(asIScriptGeneric *args) {
|
||||
auto context = asGetActiveContext();
|
||||
if (context) {
|
||||
ConsoleMode mode = ConsoleMode(reinterpret_cast<asPWORD>(
|
||||
context->GetUserData(AsUserDataType::UserData_ContextMode)));
|
||||
|
||||
auto &m = ScriptMachine::instance();
|
||||
|
||||
MessageInfo info;
|
||||
info.mode = mode;
|
||||
info.type = MessageType::Print;
|
||||
|
||||
for (int i = 0; i < args->GetArgCount(); ++i) {
|
||||
void *ref = args->GetArgAddress(i);
|
||||
int typeId = args->GetArgTypeId(i);
|
||||
|
||||
if (typeId) {
|
||||
info.message
|
||||
.append(m.debugger()->toString(ref, typeId, m.engine()))
|
||||
.append('\n');
|
||||
}
|
||||
}
|
||||
|
||||
m.outputMessage(info);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1963,6 +2016,7 @@ void ScriptMachine::registerEngineAddon(asIScriptEngine *engine) {
|
|||
RegisterScriptFile(engine);
|
||||
registerExceptionRoutines(engine);
|
||||
registerEngineAssert(engine);
|
||||
AsDirectPromise::Register(engine);
|
||||
}
|
||||
|
||||
void ScriptMachine::registerEngineAssert(asIScriptEngine *engine) {
|
||||
|
|
|
@ -160,7 +160,9 @@ protected:
|
|||
QString getCallStack(asIScriptContext *context);
|
||||
|
||||
private:
|
||||
void print(void *ref, int typeId);
|
||||
static void print(asIScriptGeneric *args);
|
||||
static void println(asIScriptGeneric *args);
|
||||
|
||||
QString getInput();
|
||||
|
||||
bool isType(asITypeInfo *tinfo, RegisteredType type);
|
||||
|
|
|
@ -225,7 +225,7 @@ void ScriptingConsole::applyScriptSettings() {
|
|||
auto dfont = QFont(set.consoleFontFamily());
|
||||
dfont.setPointSize(set.consoleFontSize());
|
||||
|
||||
auto thname = set.editorTheme();
|
||||
auto thname = set.consoleTheme();
|
||||
if (thname.isEmpty()) {
|
||||
switch (SkinManager::instance().currentTheme()) {
|
||||
case SkinManager::Theme::Dark:
|
||||
|
|
|
@ -1,3 +1,20 @@
|
|||
/*==============================================================================
|
||||
** Copyright (C) 2024-2027 WingSummer
|
||||
**
|
||||
** This program is free software: you can redistribute it and/or modify it under
|
||||
** the terms of the GNU Affero General Public License as published by the Free
|
||||
** Software Foundation, version 3.
|
||||
**
|
||||
** This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
** ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
** FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
** details.
|
||||
**
|
||||
** You should have received a copy of the GNU Affero General Public License
|
||||
** along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
** =============================================================================
|
||||
*/
|
||||
|
||||
#include "scriptingconsolebase.h"
|
||||
#include "class/wingconsolehighligher.h"
|
||||
#include "wingsyntaxhighlighter.h"
|
||||
|
|
|
@ -1,3 +1,20 @@
|
|||
/*==============================================================================
|
||||
** Copyright (C) 2024-2027 WingSummer
|
||||
**
|
||||
** This program is free software: you can redistribute it and/or modify it under
|
||||
** the terms of the GNU Affero General Public License as published by the Free
|
||||
** Software Foundation, version 3.
|
||||
**
|
||||
** This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
** ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
** FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
** details.
|
||||
**
|
||||
** You should have received a copy of the GNU Affero General Public License
|
||||
** along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
** =============================================================================
|
||||
*/
|
||||
|
||||
#ifndef SCRIPTINGCONSOLEBASE_H
|
||||
#define SCRIPTINGCONSOLEBASE_H
|
||||
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
/*==============================================================================
|
||||
** Copyright (C) 2024-2027 WingSummer
|
||||
**
|
||||
** This program is free software: you can redistribute it and/or modify it under
|
||||
** the terms of the GNU Affero General Public License as published by the Free
|
||||
** Software Foundation, version 3.
|
||||
**
|
||||
** This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
** ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
** FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
** details.
|
||||
**
|
||||
** You should have received a copy of the GNU Affero General Public License
|
||||
** along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
** =============================================================================
|
||||
*/
|
||||
|
||||
#include "scrollablelabel.h"
|
||||
|
||||
#include <QPainter>
|
||||
#include <QScrollBar>
|
||||
#include <QWheelEvent>
|
||||
|
||||
ScrollableLabel::ScrollableLabel(QWidget *parent) : QScrollArea(parent) {
|
||||
setupUI();
|
||||
}
|
||||
|
||||
void ScrollableLabel::setText(const QString &text) {
|
||||
label.setText(text);
|
||||
updateLabelSize();
|
||||
}
|
||||
|
||||
QSize ScrollableLabel::sizeHint() const { return label.sizeHint(); }
|
||||
|
||||
QSize ScrollableLabel::minimumSizeHint() const {
|
||||
return label.minimumSizeHint();
|
||||
}
|
||||
|
||||
void ScrollableLabel::wheelEvent(QWheelEvent *event) {
|
||||
if (shouldScroll()) {
|
||||
QPoint delta = event->angleDelta();
|
||||
if (!delta.isNull()) {
|
||||
horizontalScrollBar()->setValue(horizontalScrollBar()->value() -
|
||||
delta.y());
|
||||
event->accept();
|
||||
return;
|
||||
}
|
||||
}
|
||||
event->ignore();
|
||||
}
|
||||
|
||||
void ScrollableLabel::resizeEvent(QResizeEvent *event) {
|
||||
QScrollArea::resizeEvent(event);
|
||||
updateLabelSize();
|
||||
}
|
||||
|
||||
void ScrollableLabel::setupUI() {
|
||||
setWidgetResizable(false);
|
||||
setFrameShape(QFrame::NoFrame);
|
||||
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
|
||||
label.setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
|
||||
label.setWordWrap(false);
|
||||
label.setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
|
||||
|
||||
setWidget(&label);
|
||||
}
|
||||
|
||||
void ScrollableLabel::updateLabelSize() {
|
||||
QFontMetrics fm(label.font());
|
||||
const int textWidth = fm.horizontalAdvance(label.text());
|
||||
const int textHeight = fm.height();
|
||||
|
||||
if (textWidth > width()) {
|
||||
label.setFixedSize(textWidth, textHeight);
|
||||
} else {
|
||||
label.setFixedSize(width(), textHeight);
|
||||
}
|
||||
}
|
||||
|
||||
bool ScrollableLabel::shouldScroll() const {
|
||||
return label.width() > viewport()->width();
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
/*==============================================================================
|
||||
** Copyright (C) 2024-2027 WingSummer
|
||||
**
|
||||
** This program is free software: you can redistribute it and/or modify it under
|
||||
** the terms of the GNU Affero General Public License as published by the Free
|
||||
** Software Foundation, version 3.
|
||||
**
|
||||
** This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
** ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
** FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
** details.
|
||||
**
|
||||
** You should have received a copy of the GNU Affero General Public License
|
||||
** along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
** =============================================================================
|
||||
*/
|
||||
|
||||
#ifndef SCROLLABLELABEL_H
|
||||
#define SCROLLABLELABEL_H
|
||||
|
||||
#include <QLabel>
|
||||
#include <QScrollArea>
|
||||
#include <QTimer>
|
||||
|
||||
class ScrollableLabel : public QScrollArea {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ScrollableLabel(QWidget *parent = nullptr);
|
||||
|
||||
void setText(const QString &text);
|
||||
|
||||
QSize sizeHint() const override;
|
||||
|
||||
QSize minimumSizeHint() const override;
|
||||
|
||||
protected:
|
||||
void wheelEvent(QWheelEvent *event) override;
|
||||
|
||||
void resizeEvent(QResizeEvent *event) override;
|
||||
|
||||
private:
|
||||
QLabel label;
|
||||
|
||||
void setupUI();
|
||||
|
||||
void updateLabelSize();
|
||||
|
||||
bool shouldScroll() const;
|
||||
};
|
||||
|
||||
#endif // MARQUEELABEL_H
|
|
@ -131,7 +131,7 @@ MainWindow::MainWindow(SplashDialog *splash) : FramelessMainWindow() {
|
|||
m_status->addWidget(l);
|
||||
m_status->addWidget(m_lblsellen);
|
||||
|
||||
_status = new QLabel(m_status);
|
||||
_status = new ScrollableLabel(m_status);
|
||||
m_status->addPermanentWidget(_status);
|
||||
|
||||
auto separator = new QFrame(m_status);
|
||||
|
|
|
@ -466,7 +466,7 @@ signals:
|
|||
private:
|
||||
Ribbon *m_ribbon = nullptr;
|
||||
ads::CDockManager *m_dock = nullptr;
|
||||
QLabel *_status = nullptr;
|
||||
ScrollableLabel *_status = nullptr;
|
||||
|
||||
QString m_encoding;
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@
|
|||
#include <QPainter>
|
||||
#include <QPicture>
|
||||
#include <QStatusBar>
|
||||
#include <QToolTip>
|
||||
|
||||
constexpr auto EMPTY_FUNC = [] {};
|
||||
|
||||
|
@ -71,7 +72,7 @@ ScriptingDialog::ScriptingDialog(QWidget *parent)
|
|||
layout->addWidget(m_dock, 1);
|
||||
|
||||
m_status = new QStatusBar(this);
|
||||
_status = new QLabel(this);
|
||||
_status = new ScrollableLabel(this);
|
||||
m_status->addPermanentWidget(_status);
|
||||
layout->addWidget(m_status);
|
||||
buildUpContent(cw);
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
|
||||
#include "control/asobjtreewidget.h"
|
||||
#include "control/scriptingconsole.h"
|
||||
#include "control/scrollablelabel.h"
|
||||
#include "dialog/settingdialog.h"
|
||||
#include "framelessmainwindow.h"
|
||||
|
||||
|
@ -301,7 +302,7 @@ private:
|
|||
|
||||
ScriptEditor *_DebugingEditor;
|
||||
|
||||
QLabel *_status = nullptr;
|
||||
ScrollableLabel *_status = nullptr;
|
||||
};
|
||||
|
||||
#endif // SCRIPTINGDIALOG_H
|
||||
|
|
|
@ -0,0 +1,854 @@
|
|||
// url: https://github.com/romanpunia/aspromise
|
||||
// Licensed under the MIT license. Free for any type of use.
|
||||
|
||||
#ifndef AS_PROMISE_HPP
|
||||
#define AS_PROMISE_HPP
|
||||
|
||||
#ifndef PROMISE_CONFIG
|
||||
#define PROMISE_CONFIG
|
||||
#define PROMISE_TYPENAME "promise" // promise type
|
||||
#define PROMISE_VOIDPOSTFIX "_v" // promise<void> type (promise_v)
|
||||
#define PROMISE_WRAP "wrap" // promise setter function
|
||||
#define PROMISE_UNWRAP "unwrap" // promise getter function
|
||||
#define PROMISE_YIELD "yield" // promise awaiter function
|
||||
#define PROMISE_WHEN "when" // promise callback function
|
||||
#define PROMISE_EVENT "when_callback" // promise funcdef name
|
||||
#define PROMISE_PENDING "pending" // promise status checker
|
||||
#define PROMISE_AWAIT \
|
||||
"co_await" // keyword for await (C++20 coroutines one love)
|
||||
#define PROMISE_USERID 559 // promise user data identifier (any value)
|
||||
#define PROMISE_NULLID -1 // empty promise type id
|
||||
#define PROMISE_CALLBACKS true // allow <when> listener
|
||||
#endif
|
||||
#ifndef NDEBUG
|
||||
#define PROMISE_ASSERT(Expression, Message) assert((Expression) && Message)
|
||||
#define PROMISE_CHECK(Expression) (assert((Expression) >= 0))
|
||||
#else
|
||||
#define PROMISE_ASSERT(Expression, Message)
|
||||
#define PROMISE_CHECK(Expression) (Expression)
|
||||
#endif
|
||||
#ifndef ANGELSCRIPT_H
|
||||
#include <angelscript.h>
|
||||
#endif
|
||||
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <cctype>
|
||||
#include <condition_variable>
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
|
||||
#ifndef AS_PROMISE_NO_HELPERS
|
||||
/* Helper function to cleanup the script function */
|
||||
static void AsClearCallback(asIScriptFunction *Callback) {
|
||||
void *DelegateObject = Callback->GetDelegateObject();
|
||||
if (DelegateObject != nullptr)
|
||||
Callback->GetEngine()->ReleaseScriptObject(
|
||||
DelegateObject, Callback->GetDelegateObjectType());
|
||||
Callback->Release();
|
||||
}
|
||||
/* Helper function to check if context is awaiting on promise */
|
||||
static bool IsAsyncContextPending(asIScriptContext *Context) {
|
||||
return Context->GetUserData(PROMISE_USERID) != nullptr ||
|
||||
Context->GetState() == asEXECUTION_SUSPENDED;
|
||||
}
|
||||
/* Helper function to check if context is awaiting on promise or active */
|
||||
static bool IsAsyncContextBusy(asIScriptContext *Context) {
|
||||
return IsAsyncContextPending(Context) ||
|
||||
Context->GetState() == asEXECUTION_ACTIVE;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
Basic promise class that can be used for non-blocking asynchronous
|
||||
operation data exchange between AngelScript and C++ and vice-versa.
|
||||
*/
|
||||
template <typename Executor>
|
||||
class AsBasicPromise {
|
||||
private:
|
||||
/* Basically used from <any> class */
|
||||
struct Dynamic {
|
||||
union {
|
||||
asINT64 Integer;
|
||||
double Number;
|
||||
void *Object;
|
||||
};
|
||||
|
||||
int TypeId = PROMISE_NULLID;
|
||||
};
|
||||
#if PROMISE_CALLBACKS
|
||||
/* Callbacks storage */
|
||||
struct {
|
||||
std::function<void(AsBasicPromise<Executor> *)> Native;
|
||||
asIScriptFunction *Wrapper = nullptr;
|
||||
} Callbacks;
|
||||
#else
|
||||
std::condition_variable Ready;
|
||||
#endif
|
||||
private:
|
||||
asIScriptEngine *Engine;
|
||||
asIScriptContext *Context;
|
||||
std::atomic<uint32_t> RefCount;
|
||||
std::atomic<uint32_t> RefMark;
|
||||
std::mutex Update;
|
||||
Dynamic Value;
|
||||
|
||||
public:
|
||||
/* Thread safe release */
|
||||
void Release() {
|
||||
PROMISE_ASSERT(RefCount > 0, "promise is already released");
|
||||
RefMark = 0;
|
||||
if (!--RefCount) {
|
||||
ReleaseReferences(nullptr);
|
||||
this->~AsBasicPromise();
|
||||
asFreeMem((void *)this);
|
||||
}
|
||||
}
|
||||
/* Thread safe add reference */
|
||||
void AddRef() {
|
||||
PROMISE_ASSERT(RefCount < std::numeric_limits<uint32_t>::max(),
|
||||
"too many references to this promise");
|
||||
RefMark = 0;
|
||||
++RefCount;
|
||||
}
|
||||
/* For garbage collector to detect references */
|
||||
void EnumReferences(asIScriptEngine *OtherEngine) {
|
||||
if (Value.Object != nullptr && (Value.TypeId & asTYPEID_MASK_OBJECT)) {
|
||||
asITypeInfo *SubType = Engine->GetTypeInfoById(Value.TypeId);
|
||||
if ((SubType->GetFlags() & asOBJ_REF))
|
||||
OtherEngine->GCEnumCallback(Value.Object);
|
||||
else if ((SubType->GetFlags() & asOBJ_VALUE) &&
|
||||
(SubType->GetFlags() & asOBJ_GC))
|
||||
Engine->ForwardGCEnumReferences(Value.Object, SubType);
|
||||
|
||||
asITypeInfo *Type = OtherEngine->GetTypeInfoById(Value.TypeId);
|
||||
if (Type != nullptr)
|
||||
OtherEngine->GCEnumCallback(Type);
|
||||
}
|
||||
#if PROMISE_CALLBACKS
|
||||
if (Callbacks.Wrapper != nullptr) {
|
||||
void *DelegateObject = Callbacks.Wrapper->GetDelegateObject();
|
||||
if (DelegateObject != nullptr)
|
||||
OtherEngine->GCEnumCallback(DelegateObject);
|
||||
OtherEngine->GCEnumCallback(Callbacks.Wrapper);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
/* For garbage collector to release references */
|
||||
void ReleaseReferences(asIScriptEngine *) {
|
||||
if (Value.TypeId & asTYPEID_MASK_OBJECT) {
|
||||
asITypeInfo *Type = Engine->GetTypeInfoById(Value.TypeId);
|
||||
Engine->ReleaseScriptObject(Value.Object, Type);
|
||||
if (Type != nullptr)
|
||||
Type->Release();
|
||||
Clean();
|
||||
}
|
||||
#if PROMISE_CALLBACKS
|
||||
if (Callbacks.Wrapper != nullptr) {
|
||||
AsClearCallback(Callbacks.Wrapper);
|
||||
Callbacks.Wrapper = nullptr;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
/* For garbage collector to mark */
|
||||
void MarkRef() { RefMark = 0; }
|
||||
/* For garbage collector to check mark */
|
||||
bool IsRefMarked() { return RefMark == 1; }
|
||||
/* For garbage collector to check reference count */
|
||||
uint32_t GetRefCount() { return RefCount; }
|
||||
/* Receive stored type id of future value */
|
||||
int GetTypeIdOfObject() { return Value.TypeId; }
|
||||
/* Provide a native callback that should be fired when promise will be
|
||||
* settled */
|
||||
void When(std::function<void(AsBasicPromise<Executor> *)> &&NewCallback) {
|
||||
#if PROMISE_CALLBACKS
|
||||
std::unique_lock<std::mutex> Unique(Update);
|
||||
Callbacks.Native = std::move(NewCallback);
|
||||
if (Callbacks.Native && !IsPending()) {
|
||||
auto Callback = std::move(Callbacks.Native);
|
||||
Unique.unlock();
|
||||
Callback(this);
|
||||
}
|
||||
#else
|
||||
PROMISE_ASSERT(false,
|
||||
"native callback binder for <when> is not allowed");
|
||||
#endif
|
||||
}
|
||||
/* Provide a script callback that should be fired when promise will be
|
||||
* settled */
|
||||
void When(asIScriptFunction *NewCallback) {
|
||||
#if PROMISE_CALLBACKS
|
||||
std::unique_lock<std::mutex> Unique(Update);
|
||||
if (Callbacks.Wrapper != nullptr)
|
||||
AsClearCallback(Callbacks.Wrapper);
|
||||
|
||||
Callbacks.Wrapper = NewCallback;
|
||||
if (Callbacks.Wrapper != nullptr) {
|
||||
void *DelegateObject = Callbacks.Wrapper->GetDelegateObject();
|
||||
if (DelegateObject != nullptr)
|
||||
Context->GetEngine()->AddRefScriptObject(
|
||||
DelegateObject, Callbacks.Wrapper->GetDelegateObjectType());
|
||||
}
|
||||
|
||||
if (Callbacks.Wrapper != nullptr && !IsPending()) {
|
||||
Callbacks.Wrapper = nullptr;
|
||||
Unique.unlock();
|
||||
Executor()(this, Context, NewCallback);
|
||||
}
|
||||
#else
|
||||
PROMISE_ASSERT(false,
|
||||
"script callback binder for <when> is not allowed");
|
||||
#endif
|
||||
}
|
||||
/*
|
||||
Thread safe store function, this is used as promise resolver
|
||||
function, will either only store the result or store result and execute
|
||||
callback that will resume suspended context and then release the promise
|
||||
(won't destroy)
|
||||
*/
|
||||
void Store(void *RefPointer, int RefTypeId) {
|
||||
std::unique_lock<std::mutex> Unique(Update);
|
||||
PROMISE_ASSERT(Value.TypeId == PROMISE_NULLID,
|
||||
"promise should be settled only once");
|
||||
PROMISE_ASSERT(RefPointer != nullptr || RefTypeId == asTYPEID_VOID,
|
||||
"input pointer should not be null");
|
||||
PROMISE_ASSERT(Engine != nullptr,
|
||||
"promise is malformed (engine is null)");
|
||||
PROMISE_ASSERT(Context != nullptr,
|
||||
"promise is malformed (context is null)");
|
||||
|
||||
if (Value.TypeId != PROMISE_NULLID) {
|
||||
asIScriptContext *ThisContext = asGetActiveContext();
|
||||
if (!ThisContext)
|
||||
ThisContext = Context;
|
||||
ThisContext->SetException("promise is already fulfilled");
|
||||
return;
|
||||
}
|
||||
|
||||
if ((RefTypeId & asTYPEID_MASK_OBJECT)) {
|
||||
asITypeInfo *Type = Engine->GetTypeInfoById(RefTypeId);
|
||||
if (Type != nullptr)
|
||||
Type->AddRef();
|
||||
}
|
||||
|
||||
Value.TypeId = RefTypeId;
|
||||
if (Value.TypeId & asTYPEID_OBJHANDLE) {
|
||||
Value.Object = *(void **)RefPointer;
|
||||
} else if (Value.TypeId & asTYPEID_MASK_OBJECT) {
|
||||
Value.Object = Engine->CreateScriptObjectCopy(
|
||||
RefPointer, Engine->GetTypeInfoById(Value.TypeId));
|
||||
} else if (RefPointer != nullptr) {
|
||||
Value.Integer = 0;
|
||||
int Size = Engine->GetSizeOfPrimitiveType(Value.TypeId);
|
||||
memcpy(&Value.Integer, RefPointer, Size);
|
||||
}
|
||||
|
||||
bool SuspendOwned =
|
||||
Context->GetUserData(PROMISE_USERID) == (void *)this;
|
||||
if (SuspendOwned)
|
||||
Context->SetUserData(nullptr, PROMISE_USERID);
|
||||
|
||||
bool WantsResume =
|
||||
(Context->GetState() == asEXECUTION_SUSPENDED && SuspendOwned);
|
||||
#if PROMISE_CALLBACKS
|
||||
auto NativeCallback = std::move(Callbacks.Native);
|
||||
auto *WrapperCallback = Callbacks.Wrapper;
|
||||
Callbacks.Wrapper = nullptr;
|
||||
Unique.unlock();
|
||||
|
||||
if (NativeCallback != nullptr)
|
||||
NativeCallback(this);
|
||||
|
||||
if (WrapperCallback != nullptr)
|
||||
Executor()(this, Context, WrapperCallback);
|
||||
#else
|
||||
Ready.notify_all();
|
||||
Unique.unlock();
|
||||
#endif
|
||||
if (WantsResume)
|
||||
Executor()(this, Context);
|
||||
}
|
||||
/* Thread safe store function, a little easier for C++ usage */
|
||||
void Store(void *RefPointer, const char *TypeName) {
|
||||
PROMISE_ASSERT(Engine != nullptr,
|
||||
"promise is malformed (engine is null)");
|
||||
PROMISE_ASSERT(TypeName != nullptr, "typename should not be null");
|
||||
Store(RefPointer, Engine->GetTypeIdByDecl(TypeName));
|
||||
}
|
||||
/* Thread safe store function, for promise<void> */
|
||||
void StoreVoid() { Store(nullptr, asTYPEID_VOID); }
|
||||
/* Thread safe retrieve function, non-blocking try-retrieve future value */
|
||||
bool Retrieve(void *RefPointer, int RefTypeId) {
|
||||
PROMISE_ASSERT(Engine != nullptr,
|
||||
"promise is malformed (engine is null)");
|
||||
PROMISE_ASSERT(RefPointer != nullptr,
|
||||
"output pointer should not be null");
|
||||
if (Value.TypeId == PROMISE_NULLID)
|
||||
return false;
|
||||
|
||||
if (RefTypeId & asTYPEID_OBJHANDLE) {
|
||||
if ((Value.TypeId & asTYPEID_MASK_OBJECT)) {
|
||||
if ((Value.TypeId & asTYPEID_HANDLETOCONST) &&
|
||||
!(RefTypeId & asTYPEID_HANDLETOCONST))
|
||||
return false;
|
||||
|
||||
Engine->RefCastObject(Value.Object,
|
||||
Engine->GetTypeInfoById(Value.TypeId),
|
||||
Engine->GetTypeInfoById(RefTypeId),
|
||||
reinterpret_cast<void **>(RefPointer));
|
||||
if (*(asPWORD *)RefPointer == 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
} else if (RefTypeId & asTYPEID_MASK_OBJECT) {
|
||||
if (Value.TypeId == RefTypeId) {
|
||||
Engine->AssignScriptObject(
|
||||
RefPointer, Value.Object,
|
||||
Engine->GetTypeInfoById(Value.TypeId));
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
int Size1 = Engine->GetSizeOfPrimitiveType(Value.TypeId);
|
||||
int Size2 = Engine->GetSizeOfPrimitiveType(RefTypeId);
|
||||
PROMISE_ASSERT(Size1 == Size2,
|
||||
"cannot map incompatible primitive types");
|
||||
|
||||
if (Size1 == Size2) {
|
||||
memcpy(RefPointer, &Value.Integer, Size1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
/* Thread safe retrieve function, also non-blocking, another syntax is used
|
||||
*/
|
||||
void *Retrieve() {
|
||||
RetrieveVoid();
|
||||
if (Value.TypeId == PROMISE_NULLID)
|
||||
return nullptr;
|
||||
|
||||
if (Value.TypeId & asTYPEID_OBJHANDLE)
|
||||
return &Value.Object;
|
||||
else if (Value.TypeId & asTYPEID_MASK_OBJECT)
|
||||
return Value.Object;
|
||||
else if (Value.TypeId <= asTYPEID_DOUBLE ||
|
||||
Value.TypeId & asTYPEID_MASK_SEQNBR)
|
||||
return &Value.Integer;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
/* Thread safe retrieve function */
|
||||
void RetrieveVoid() {
|
||||
std::unique_lock<std::mutex> Unique(Update);
|
||||
asIScriptContext *ThisContext = asGetActiveContext();
|
||||
if (ThisContext != nullptr && IsPending())
|
||||
ThisContext->SetException("promise is still pending");
|
||||
}
|
||||
/* Can be used to check if promise is still pending */
|
||||
bool IsPending() { return Value.TypeId == PROMISE_NULLID; }
|
||||
/*
|
||||
This function should be called before retrieving the value
|
||||
from promise, it will either suspend current context and add
|
||||
reference to this promise if it is still pending or do nothing
|
||||
if promise was already settled
|
||||
*/
|
||||
AsBasicPromise *YieldIf() {
|
||||
std::unique_lock<std::mutex> Unique(Update);
|
||||
if (Value.TypeId == PROMISE_NULLID && Context != nullptr &&
|
||||
Context->Suspend() >= 0)
|
||||
Context->SetUserData(this, PROMISE_USERID);
|
||||
|
||||
return this;
|
||||
}
|
||||
/*
|
||||
This function can be used to await for promise
|
||||
within C++ code (blocking style)
|
||||
*/
|
||||
AsBasicPromise *WaitIf() {
|
||||
if (!IsPending())
|
||||
return this;
|
||||
|
||||
std::unique_lock<std::mutex> Unique(Update);
|
||||
#if PROMISE_CALLBACKS
|
||||
if (IsPending()) {
|
||||
std::condition_variable Ready;
|
||||
Callbacks.Native = [&Ready](AsBasicPromise<Executor> *) {
|
||||
Ready.notify_all();
|
||||
};
|
||||
Ready.wait(Unique, [this]() { return !IsPending(); });
|
||||
}
|
||||
#else
|
||||
if (IsPending())
|
||||
Ready.wait(Unique, [this]() { return !IsPending(); });
|
||||
#endif
|
||||
return this;
|
||||
}
|
||||
|
||||
private:
|
||||
/*
|
||||
Construct a promise, notify GC, set value to none,
|
||||
grab a reference to script context
|
||||
*/
|
||||
AsBasicPromise(asIScriptContext *NewContext) noexcept
|
||||
: Engine(nullptr), Context(NewContext), RefCount(1), RefMark(0) {
|
||||
PROMISE_ASSERT(Context != nullptr, "context should not be null");
|
||||
Engine = Context->GetEngine();
|
||||
Engine->NotifyGarbageCollectorOfNewObject(
|
||||
this, Engine->GetTypeInfoByName(PROMISE_TYPENAME));
|
||||
Clean();
|
||||
}
|
||||
/* Reset value to none */
|
||||
void Clean() {
|
||||
memset(&Value, 0, sizeof(Value));
|
||||
Value.TypeId = PROMISE_NULLID;
|
||||
}
|
||||
|
||||
public:
|
||||
/* AsBasicPromise creation function, for use within C++ */
|
||||
static AsBasicPromise *
|
||||
Create(asIScriptContext *Context = asGetActiveContext()) {
|
||||
return new (asAllocMem(sizeof(AsBasicPromise))) AsBasicPromise(Context);
|
||||
}
|
||||
/* AsBasicPromise creation function, for use within AngelScript */
|
||||
static AsBasicPromise *CreateFactory(void *_Ref, int TypeId) {
|
||||
AsBasicPromise *Future = new (asAllocMem(sizeof(AsBasicPromise)))
|
||||
AsBasicPromise(asGetActiveContext());
|
||||
if (TypeId != asTYPEID_VOID)
|
||||
Future->Store(_Ref, TypeId);
|
||||
|
||||
return Future;
|
||||
}
|
||||
/* AsBasicPromise creation function, for use within AngelScript (void
|
||||
* promise) */
|
||||
static AsBasicPromise *CreateFactoryVoid(void *_Ref, int TypeId) {
|
||||
return Create();
|
||||
}
|
||||
/*
|
||||
Interface registration, note: promise<void> is not supported,
|
||||
instead use promise_v when internal datatype is not intended,
|
||||
promise will be an object handle with GC behaviours, default
|
||||
constructed promise will be pending otherwise early settled
|
||||
*/
|
||||
static void Register(asIScriptEngine *Engine) {
|
||||
using Type = AsBasicPromise<Executor>;
|
||||
PROMISE_ASSERT(Engine != nullptr, "script engine should not be null");
|
||||
PROMISE_CHECK(
|
||||
Engine->RegisterObjectType(PROMISE_TYPENAME "<class T>", 0,
|
||||
asOBJ_REF | asOBJ_GC | asOBJ_TEMPLATE));
|
||||
PROMISE_CHECK(Engine->RegisterObjectBehaviour(
|
||||
PROMISE_TYPENAME "<T>", asBEHAVE_FACTORY,
|
||||
PROMISE_TYPENAME "<T>@ f(?&in)", asFUNCTION(Type::CreateFactory),
|
||||
asCALL_CDECL));
|
||||
PROMISE_CHECK(Engine->RegisterObjectBehaviour(
|
||||
PROMISE_TYPENAME "<T>", asBEHAVE_TEMPLATE_CALLBACK,
|
||||
"bool f(int&in, bool&out)", asFUNCTION(Type::TemplateCallback),
|
||||
asCALL_CDECL));
|
||||
PROMISE_CHECK(Engine->RegisterObjectBehaviour(
|
||||
PROMISE_TYPENAME "<T>", asBEHAVE_ADDREF, "void f()",
|
||||
asMETHOD(Type, AddRef), asCALL_THISCALL));
|
||||
PROMISE_CHECK(Engine->RegisterObjectBehaviour(
|
||||
PROMISE_TYPENAME "<T>", asBEHAVE_RELEASE, "void f()",
|
||||
asMETHOD(Type, Release), asCALL_THISCALL));
|
||||
PROMISE_CHECK(Engine->RegisterObjectBehaviour(
|
||||
PROMISE_TYPENAME "<T>", asBEHAVE_SETGCFLAG, "void f()",
|
||||
asMETHOD(Type, MarkRef), asCALL_THISCALL));
|
||||
PROMISE_CHECK(Engine->RegisterObjectBehaviour(
|
||||
PROMISE_TYPENAME "<T>", asBEHAVE_GETGCFLAG, "bool f()",
|
||||
asMETHOD(Type, IsRefMarked), asCALL_THISCALL));
|
||||
PROMISE_CHECK(Engine->RegisterObjectBehaviour(
|
||||
PROMISE_TYPENAME "<T>", asBEHAVE_GETREFCOUNT, "int f()",
|
||||
asMETHOD(Type, GetRefCount), asCALL_THISCALL));
|
||||
PROMISE_CHECK(Engine->RegisterObjectBehaviour(
|
||||
PROMISE_TYPENAME "<T>", asBEHAVE_ENUMREFS, "void f(int&in)",
|
||||
asMETHOD(Type, EnumReferences), asCALL_THISCALL));
|
||||
PROMISE_CHECK(Engine->RegisterObjectBehaviour(
|
||||
PROMISE_TYPENAME "<T>", asBEHAVE_RELEASEREFS, "void f(int&in)",
|
||||
asMETHOD(Type, ReleaseReferences), asCALL_THISCALL));
|
||||
PROMISE_CHECK(Engine->RegisterObjectMethod(
|
||||
PROMISE_TYPENAME "<T>", "void " PROMISE_WRAP "(?&in)",
|
||||
asMETHODPR(Type, Store, (void *, int), void), asCALL_THISCALL));
|
||||
PROMISE_CHECK(Engine->RegisterObjectMethod(
|
||||
PROMISE_TYPENAME "<T>", "T& " PROMISE_UNWRAP "()",
|
||||
asMETHODPR(Type, Retrieve, (), void *), asCALL_THISCALL));
|
||||
PROMISE_CHECK(Engine->RegisterObjectMethod(
|
||||
PROMISE_TYPENAME "<T>",
|
||||
PROMISE_TYPENAME "<T>@+ " PROMISE_YIELD "()",
|
||||
asMETHOD(Type, YieldIf), asCALL_THISCALL));
|
||||
PROMISE_CHECK(Engine->RegisterObjectMethod(
|
||||
PROMISE_TYPENAME "<T>", "bool " PROMISE_PENDING "()",
|
||||
asMETHOD(Type, IsPending), asCALL_THISCALL));
|
||||
#if PROMISE_CALLBACKS
|
||||
PROMISE_CHECK(Engine->RegisterFuncdef(
|
||||
"void " PROMISE_TYPENAME "<T>::" PROMISE_EVENT "(promise<T>@+)"));
|
||||
PROMISE_CHECK(Engine->RegisterObjectMethod(
|
||||
PROMISE_TYPENAME "<T>", "void " PROMISE_WHEN "(" PROMISE_EVENT "@)",
|
||||
asMETHODPR(Type, When, (asIScriptFunction *), void),
|
||||
asCALL_THISCALL));
|
||||
#endif
|
||||
PROMISE_CHECK(Engine->RegisterObjectType(
|
||||
PROMISE_TYPENAME PROMISE_VOIDPOSTFIX, 0, asOBJ_REF | asOBJ_GC));
|
||||
PROMISE_CHECK(Engine->RegisterObjectBehaviour(
|
||||
PROMISE_TYPENAME PROMISE_VOIDPOSTFIX, asBEHAVE_FACTORY,
|
||||
PROMISE_TYPENAME PROMISE_VOIDPOSTFIX "@ f()",
|
||||
asFUNCTION(Type::CreateFactoryVoid), asCALL_CDECL));
|
||||
PROMISE_CHECK(Engine->RegisterObjectBehaviour(
|
||||
PROMISE_TYPENAME PROMISE_VOIDPOSTFIX, asBEHAVE_ADDREF, "void f()",
|
||||
asMETHOD(Type, AddRef), asCALL_THISCALL));
|
||||
PROMISE_CHECK(Engine->RegisterObjectBehaviour(
|
||||
PROMISE_TYPENAME PROMISE_VOIDPOSTFIX, asBEHAVE_RELEASE, "void f()",
|
||||
asMETHOD(Type, Release), asCALL_THISCALL));
|
||||
PROMISE_CHECK(Engine->RegisterObjectBehaviour(
|
||||
PROMISE_TYPENAME PROMISE_VOIDPOSTFIX, asBEHAVE_SETGCFLAG,
|
||||
"void f()", asMETHOD(Type, MarkRef), asCALL_THISCALL));
|
||||
PROMISE_CHECK(Engine->RegisterObjectBehaviour(
|
||||
PROMISE_TYPENAME PROMISE_VOIDPOSTFIX, asBEHAVE_GETGCFLAG,
|
||||
"bool f()", asMETHOD(Type, IsRefMarked), asCALL_THISCALL));
|
||||
PROMISE_CHECK(Engine->RegisterObjectBehaviour(
|
||||
PROMISE_TYPENAME PROMISE_VOIDPOSTFIX, asBEHAVE_GETREFCOUNT,
|
||||
"int f()", asMETHOD(Type, GetRefCount), asCALL_THISCALL));
|
||||
PROMISE_CHECK(Engine->RegisterObjectBehaviour(
|
||||
PROMISE_TYPENAME PROMISE_VOIDPOSTFIX, asBEHAVE_ENUMREFS,
|
||||
"void f(int&in)", asMETHOD(Type, EnumReferences), asCALL_THISCALL));
|
||||
PROMISE_CHECK(Engine->RegisterObjectBehaviour(
|
||||
PROMISE_TYPENAME PROMISE_VOIDPOSTFIX, asBEHAVE_RELEASEREFS,
|
||||
"void f(int&in)", asMETHOD(Type, ReleaseReferences),
|
||||
asCALL_THISCALL));
|
||||
PROMISE_CHECK(Engine->RegisterObjectMethod(
|
||||
PROMISE_TYPENAME PROMISE_VOIDPOSTFIX, "void " PROMISE_WRAP "()",
|
||||
asMETHODPR(Type, StoreVoid, (), void), asCALL_THISCALL));
|
||||
PROMISE_CHECK(Engine->RegisterObjectMethod(
|
||||
PROMISE_TYPENAME PROMISE_VOIDPOSTFIX, "void " PROMISE_UNWRAP "()",
|
||||
asMETHODPR(Type, RetrieveVoid, (), void), asCALL_THISCALL));
|
||||
PROMISE_CHECK(Engine->RegisterObjectMethod(
|
||||
PROMISE_TYPENAME PROMISE_VOIDPOSTFIX,
|
||||
PROMISE_TYPENAME PROMISE_VOIDPOSTFIX "@+ " PROMISE_YIELD "()",
|
||||
asMETHOD(Type, YieldIf), asCALL_THISCALL));
|
||||
PROMISE_CHECK(Engine->RegisterObjectMethod(
|
||||
PROMISE_TYPENAME PROMISE_VOIDPOSTFIX, "bool " PROMISE_PENDING "()",
|
||||
asMETHOD(Type, IsPending), asCALL_THISCALL));
|
||||
#if PROMISE_CALLBACKS
|
||||
PROMISE_CHECK(
|
||||
Engine->RegisterFuncdef("void " PROMISE_TYPENAME PROMISE_VOIDPOSTFIX
|
||||
"::" PROMISE_EVENT "(promise_v@+)"));
|
||||
PROMISE_CHECK(Engine->RegisterObjectMethod(
|
||||
PROMISE_TYPENAME PROMISE_VOIDPOSTFIX,
|
||||
"void " PROMISE_WHEN "(" PROMISE_EVENT "@)",
|
||||
asMETHODPR(Type, When, (asIScriptFunction *), void),
|
||||
asCALL_THISCALL));
|
||||
#endif
|
||||
}
|
||||
|
||||
private:
|
||||
/* Template callback function for compiler, copy-paste from <array> class */
|
||||
static bool TemplateCallback(asITypeInfo *Info, bool &DontGarbageCollect) {
|
||||
int TypeId = Info->GetSubTypeId();
|
||||
if (TypeId == asTYPEID_VOID)
|
||||
return false;
|
||||
|
||||
if ((TypeId & asTYPEID_MASK_OBJECT) && !(TypeId & asTYPEID_OBJHANDLE)) {
|
||||
asIScriptEngine *Engine = Info->GetEngine();
|
||||
asITypeInfo *SubType = Engine->GetTypeInfoById(TypeId);
|
||||
asDWORD Flags = SubType->GetFlags();
|
||||
|
||||
if ((Flags & asOBJ_VALUE) && !(Flags & asOBJ_POD)) {
|
||||
bool Found = false;
|
||||
for (size_t i = 0; i < SubType->GetBehaviourCount(); i++) {
|
||||
asEBehaviours Behaviour;
|
||||
asIScriptFunction *Func =
|
||||
SubType->GetBehaviourByIndex((int)i, &Behaviour);
|
||||
if (Behaviour != asBEHAVE_CONSTRUCT)
|
||||
continue;
|
||||
|
||||
if (Func->GetParamCount() == 0) {
|
||||
Found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!Found) {
|
||||
Engine->WriteMessage(
|
||||
PROMISE_TYPENAME, 0, 0, asMSGTYPE_ERROR,
|
||||
"The subtype has no default constructor");
|
||||
return false;
|
||||
}
|
||||
} else if ((Flags & asOBJ_REF)) {
|
||||
bool Found = false;
|
||||
if (!Engine->GetEngineProperty(
|
||||
asEP_DISALLOW_VALUE_ASSIGN_FOR_REF_TYPE)) {
|
||||
for (size_t i = 0; i < SubType->GetFactoryCount(); i++) {
|
||||
asIScriptFunction *Function =
|
||||
SubType->GetFactoryByIndex((int)i);
|
||||
if (Function->GetParamCount() == 0) {
|
||||
Found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!Found) {
|
||||
Engine->WriteMessage(PROMISE_TYPENAME, 0, 0,
|
||||
asMSGTYPE_ERROR,
|
||||
"The subtype has no default factory");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!(Flags & asOBJ_GC))
|
||||
DontGarbageCollect = true;
|
||||
} else if (!(TypeId & asTYPEID_OBJHANDLE)) {
|
||||
DontGarbageCollect = true;
|
||||
} else {
|
||||
asITypeInfo *SubType = Info->GetEngine()->GetTypeInfoById(TypeId);
|
||||
asDWORD Flags = SubType->GetFlags();
|
||||
|
||||
if (!(Flags & asOBJ_GC)) {
|
||||
if ((Flags & asOBJ_SCRIPT_OBJECT)) {
|
||||
if ((Flags & asOBJ_NOINHERIT))
|
||||
DontGarbageCollect = true;
|
||||
} else
|
||||
DontGarbageCollect = true;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
#ifndef AS_PROMISE_NO_GENERATOR
|
||||
/*
|
||||
A fast and minimal code generator function for custom syntax of promise
|
||||
class, it takes raw code input with <await> syntax and returns code that use
|
||||
un-wrappers.
|
||||
*/
|
||||
static char *
|
||||
AsGeneratePromiseEntrypoints(const char *Text, size_t *InoutTextSize,
|
||||
void *(*AllocateMemory)(size_t) = &asAllocMem,
|
||||
void (*FreeMemory)(void *) = &asFreeMem) {
|
||||
PROMISE_ASSERT(Text != nullptr, "script code should not be null");
|
||||
PROMISE_ASSERT(InoutTextSize != nullptr,
|
||||
"script code size should not be null");
|
||||
PROMISE_ASSERT(AllocateMemory != nullptr,
|
||||
"memory allocation function should not be null");
|
||||
PROMISE_ASSERT(FreeMemory != nullptr,
|
||||
"memory deallocation function should not be null");
|
||||
const char Match[] = PROMISE_AWAIT " ";
|
||||
size_t Size = *InoutTextSize;
|
||||
char *Code = (char *)AllocateMemory(Size + 1);
|
||||
size_t MatchSize = sizeof(Match) - 1;
|
||||
size_t Offset = 0;
|
||||
memcpy(Code, Text, Size);
|
||||
Code[Size] = '\0';
|
||||
|
||||
while (Offset < Size) {
|
||||
char U = Code[Offset];
|
||||
if (U == '/' && Offset + 1 < Size &&
|
||||
(Code[Offset + 1] == '/' || Code[Offset + 1] == '*')) {
|
||||
if (Code[++Offset] == '*') {
|
||||
while (Offset + 1 < Size) {
|
||||
char N = Code[Offset++];
|
||||
if (N == '*' && Code[Offset++] == '/')
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
while (Offset < Size) {
|
||||
char N = Code[Offset++];
|
||||
if (N == '\r' || N == '\n')
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
} else if (U == '\"' || U == '\'') {
|
||||
++Offset;
|
||||
while (Offset < Size) {
|
||||
size_t LastOffset = Offset++;
|
||||
if (Code[LastOffset] != U)
|
||||
continue;
|
||||
|
||||
if (LastOffset < 1 || Code[LastOffset - 1] != '\\')
|
||||
break;
|
||||
|
||||
if (LastOffset > 1 && Code[LastOffset - 2] == '\\')
|
||||
break;
|
||||
}
|
||||
|
||||
continue;
|
||||
} else if (Size - Offset < MatchSize ||
|
||||
memcmp(Code + Offset, Match, MatchSize) != 0) {
|
||||
++Offset;
|
||||
continue;
|
||||
}
|
||||
|
||||
size_t Start = Offset + MatchSize;
|
||||
while (Start < Size) {
|
||||
if (!isspace((uint8_t)Code[Start]))
|
||||
break;
|
||||
++Start;
|
||||
}
|
||||
|
||||
int32_t Brackets = 0;
|
||||
size_t End = Start;
|
||||
while (End < Size) {
|
||||
char V = Code[End];
|
||||
if (V == ')') {
|
||||
if (--Brackets < 0)
|
||||
break;
|
||||
} else if (V == '\"' || V == '\'') {
|
||||
++End;
|
||||
while (End < Size) {
|
||||
size_t LastEnd = End++;
|
||||
if (Code[LastEnd] != V)
|
||||
continue;
|
||||
|
||||
if (LastEnd < 1 || Code[LastEnd - 1] != '\\')
|
||||
break;
|
||||
|
||||
if (LastEnd > 1 && Code[LastEnd - 2] == '\\')
|
||||
break;
|
||||
}
|
||||
--End;
|
||||
} else if (V == ';')
|
||||
break;
|
||||
else if (V == '(')
|
||||
++Brackets;
|
||||
End++;
|
||||
}
|
||||
|
||||
if (End == Start) {
|
||||
Offset = End;
|
||||
continue;
|
||||
}
|
||||
|
||||
const char Generator[] = ")." PROMISE_YIELD "()." PROMISE_UNWRAP "()";
|
||||
char *Left = Code, *Middle = Code + Start, *Right = Code + End;
|
||||
size_t LeftSize = Offset;
|
||||
size_t MiddleSize = End - Start;
|
||||
size_t GeneratorSize = sizeof(Generator) - 1;
|
||||
size_t RightSize = Size - Offset;
|
||||
size_t SubstringSize =
|
||||
LeftSize + MiddleSize + GeneratorSize + RightSize;
|
||||
size_t PrevSize = End - Offset;
|
||||
size_t NewSize = MiddleSize + GeneratorSize + 1;
|
||||
|
||||
char *Substring = (char *)AllocateMemory(SubstringSize + 1);
|
||||
memcpy(Substring, Left, LeftSize);
|
||||
memcpy(Substring + LeftSize, "(", 1);
|
||||
memcpy(Substring + LeftSize + 1, Middle, MiddleSize);
|
||||
memcpy(Substring + LeftSize + 1 + MiddleSize, Generator, GeneratorSize);
|
||||
memcpy(Substring + LeftSize + 1 + MiddleSize + GeneratorSize, Right,
|
||||
RightSize);
|
||||
Substring[SubstringSize] = '\0';
|
||||
FreeMemory(Code);
|
||||
|
||||
size_t NestedSize = Offset + MiddleSize;
|
||||
char Prev = Substring[NestedSize];
|
||||
Substring[NestedSize] = '\0';
|
||||
bool IsRecursive =
|
||||
(strstr(Substring + Offset, PROMISE_AWAIT) != nullptr);
|
||||
Substring[NestedSize] = Prev;
|
||||
|
||||
Code = Substring;
|
||||
Size -= PrevSize;
|
||||
Size += NewSize;
|
||||
if (!IsRecursive)
|
||||
Offset += MiddleSize + GeneratorSize;
|
||||
}
|
||||
|
||||
*InoutTextSize = Size;
|
||||
return Code;
|
||||
}
|
||||
#endif
|
||||
#ifndef AS_PROMISE_NO_DEFAULTS
|
||||
/*
|
||||
Basic promise settle executor, will
|
||||
resume context at thread that has
|
||||
settled the promise.
|
||||
*/
|
||||
struct AsDirectExecutor {
|
||||
/* Called after suspend, this method will probably be inlined anyways */
|
||||
inline void operator()(AsBasicPromise<AsDirectExecutor> *Promise,
|
||||
asIScriptContext *Context) {
|
||||
/*
|
||||
Context should be suspended at this moment but if for
|
||||
some reason it went active between function calls
|
||||
(multithreaded) then user is responsible for this task to be properly
|
||||
queued or exception should thrown if possible
|
||||
*/
|
||||
Context->Execute();
|
||||
}
|
||||
/* Called after suspend, for callback execution */
|
||||
inline void operator()(AsBasicPromise<AsDirectExecutor> *Promise,
|
||||
asIScriptContext *Context,
|
||||
asIScriptFunction *Callback) {
|
||||
/*
|
||||
Callback control flow:
|
||||
If main context is active: execute nested call on
|
||||
current context If main context is suspended: execute on newly
|
||||
created context Otherwise: execute on current context
|
||||
*/
|
||||
asEContextState State = Context->GetState();
|
||||
auto Execute = [&Promise, &Context, &Callback]() {
|
||||
PROMISE_CHECK(Context->Prepare(Callback));
|
||||
PROMISE_CHECK(Context->SetArgObject(0, Promise));
|
||||
Context->Execute();
|
||||
};
|
||||
if (State == asEXECUTION_ACTIVE) {
|
||||
PROMISE_CHECK(Context->PushState());
|
||||
Execute();
|
||||
PROMISE_CHECK(Context->PopState());
|
||||
} else if (State == asEXECUTION_SUSPENDED) {
|
||||
asIScriptEngine *Engine = Context->GetEngine();
|
||||
Context = Engine->RequestContext();
|
||||
Execute();
|
||||
Engine->ReturnContext(Context);
|
||||
} else
|
||||
Execute();
|
||||
|
||||
/* Cleanup referenced resources */
|
||||
AsClearCallback(Callback);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
Executor that notifies prepared context
|
||||
whenever promise settles.
|
||||
*/
|
||||
struct AsReactiveExecutor {
|
||||
typedef std::function<void(AsBasicPromise<AsReactiveExecutor> *,
|
||||
asIScriptFunction *)>
|
||||
ReactiveCallback;
|
||||
|
||||
/* Called after suspend, this method will probably be inlined anyways */
|
||||
inline void operator()(AsBasicPromise<AsReactiveExecutor> *Promise,
|
||||
asIScriptContext *Context) {
|
||||
ReactiveCallback &Execute = GetCallback(Context);
|
||||
Execute(Promise, nullptr);
|
||||
}
|
||||
/* Called after suspend, for callback execution */
|
||||
inline void operator()(AsBasicPromise<AsReactiveExecutor> *Promise,
|
||||
asIScriptContext *Context,
|
||||
asIScriptFunction *Callback) {
|
||||
ReactiveCallback &Execute = GetCallback(Context);
|
||||
Execute(Promise, Callback);
|
||||
}
|
||||
static void SetCallback(asIScriptContext *Context,
|
||||
ReactiveCallback *Callback) {
|
||||
PROMISE_ASSERT(!Callback || *Callback, "invalid reactive callback");
|
||||
Context->SetUserData((void *)Callback, 1022);
|
||||
}
|
||||
static ReactiveCallback &GetCallback(asIScriptContext *Context) {
|
||||
ReactiveCallback *Callback =
|
||||
(ReactiveCallback *)Context->GetUserData(1022);
|
||||
PROMISE_ASSERT(Callback != nullptr,
|
||||
"missing reactive callback on context");
|
||||
return *Callback;
|
||||
}
|
||||
};
|
||||
|
||||
using AsDirectPromise = AsBasicPromise<AsDirectExecutor>;
|
||||
using AsReactivePromise = AsBasicPromise<AsReactiveExecutor>;
|
||||
#endif
|
||||
#endif
|
Loading…
Reference in New Issue