Fixed method calls

This commit is contained in:
ksuprynowicz 2022-11-20 17:45:25 +01:00
parent 640cee0360
commit e680b5dc72
5 changed files with 108 additions and 33 deletions

View file

@ -23,6 +23,10 @@
#include "ScriptEngineLogging.h"
#include "ScriptManager.h"
Vec3::~Vec3() {
qDebug(scriptengine) << "ScriptMethodV8Proxy destroyed";
printf("ScriptMethodV8Proxy destroyed");
}
float Vec3::orientedAngle(const glm::vec3& v1, const glm::vec3& v2, const glm::vec3& v3) {
float radians = glm::orientedAngle(glm::normalize(v1), glm::normalize(v2), glm::normalize(v3));

View file

@ -418,6 +418,10 @@ private:
const glm::vec3& RIGHT() { return Vectors::RIGHT; }
const glm::vec3& UP() { return Vectors::UP; }
const glm::vec3& FRONT() { return Vectors::FRONT; }
//V8TODO delete after V8 works - used only for debugging
public:
virtual ~Vec3();
};
#endif // hifi_Vec3_h

View file

@ -184,7 +184,9 @@ public: // not for public use, but I don't like how Qt strings this along with p
using ObjectWrapperMap = QMap<QObject*, QWeakPointer<ScriptObjectV8Proxy>>;
mutable QMutex _qobjectWrapperMapProtect;
ObjectWrapperMap _qobjectWrapperMap;
// Second map, from which wrappers are removed by script engine upon deletion
// V8TODO add a V8 callback that removes pointer from the map so that it gets deleted
QMap<QObject*, QSharedPointer<ScriptObjectV8Proxy>> _qobjectWrapperMapV8;
protected:
// like `newFunction`, but allows mapping inline C++ lambdas with captures as callable V8ScriptValues

View file

@ -33,7 +33,7 @@ Q_DECLARE_METATYPE(QSharedPointer<ScriptVariantV8Proxy>)
static const void *internalPointsToQObjectProxy = (void *)0x13370000;
static const void *internalPointsToQVariantProxy = (void *)0x13371000;
static const void *internalPointsToSignalProxy = (void *)0x13372000;
static const void *internalPointsToMethodProxy = (void *)0x13372000;
static const void *internalPointsToMethodProxy = (void *)0x13373000;
// Used strictly to replace the "this" object value for property access. May expand to a full context element
// if we find it necessary to, but hopefully not needed
@ -114,9 +114,10 @@ V8ScriptValue ScriptObjectV8Proxy::newQObject(ScriptEngineV8* engine, QObject* o
return V8ScriptValue(engine->getIsolate(), proxy.get()->toV8Value());
}
}
// V8TODO add a V8 callback that removes pointer from the map so that it gets deleted
// register the wrapper with the engine and make sure it cleans itself up
engine->_qobjectWrapperMap.insert(object, proxy);
engine->_qobjectWrapperMapV8.insert(object, proxy);
QPointer<ScriptEngineV8> enginePtr = engine;
object->connect(object, &QObject::destroyed, engine, [enginePtr, object]() {
if (!enginePtr) return;
@ -137,13 +138,16 @@ ScriptObjectV8Proxy* ScriptObjectV8Proxy::unwrapProxy(const V8ScriptValue& val)
v8::HandleScope handleScope(const_cast<v8::Isolate*>(val.constGetIsolate()));
auto v8Value = val.constGet();
if (!v8Value->IsObject()) {
qDebug(scriptengine) << "Cannot unwrap proxy - value is not an object";
return nullptr;
}
v8::Local<v8::Object> v8Object = v8::Local<v8::Object>::Cast(v8Value);
if (v8Object->InternalFieldCount() != 2) {
qDebug(scriptengine) << "Cannot unwrap proxy - wrong number of internal fields";
return nullptr;
}
if (v8Object->GetAlignedPointerFromInternalField(0) == internalPointsToQObjectProxy) {
if (v8Object->GetAlignedPointerFromInternalField(0) != internalPointsToQObjectProxy) {
qDebug(scriptengine) << "Cannot unwrap proxy - internal fields don't point to object proxy";
return nullptr;
}
return reinterpret_cast<ScriptObjectV8Proxy*>(v8Object->GetAlignedPointerFromInternalField(1));
@ -155,6 +159,7 @@ QObject* ScriptObjectV8Proxy::unwrap(const V8ScriptValue& val) {
}
ScriptObjectV8Proxy::~ScriptObjectV8Proxy() {
qDebug(scriptengine) << "Deleting object proxy: " << name();
if (_ownsObject) {
QObject* qobject = _object;
if(qobject) qobject->deleteLater();
@ -170,11 +175,18 @@ void ScriptObjectV8Proxy::investigate() {
v8::Context::Scope contextScope(_engine->getContext());
auto objectTemplate = _v8ObjectTemplate.Get(_engine->getIsolate());
objectTemplate->SetInternalFieldCount(3);
objectTemplate->SetInternalFieldCount(2);
objectTemplate->SetHandler(v8::NamedPropertyHandlerConfiguration(v8Get, v8Set));
const QMetaObject* metaObject = qobject->metaObject();
qDebug(scriptengine) << "Investigate: " << metaObject->className();
if (QString("Vec3") == metaObject->className()) {
printf("Vec3");
}
if (QString("ConsoleScriptingInterface") == metaObject->className()) {
printf("ConsoleScriptingInterface");
}
// discover properties
int startIdx = _wrapOptions & ScriptEngine::ExcludeSuperClassProperties ? metaObject->propertyOffset() : 0;
int num = metaObject->propertyCount();
@ -182,6 +194,7 @@ void ScriptObjectV8Proxy::investigate() {
QMetaProperty prop = metaObject->property(idx);
if (!prop.isScriptable()) continue;
qDebug(scriptengine) << "Investigate: " << metaObject->className() << " Property: " << prop.name();
// always exclude child objects (at least until we decide otherwise)
int metaTypeId = prop.userType();
if (metaTypeId != QMetaType::UnknownType) {
@ -204,6 +217,7 @@ void ScriptObjectV8Proxy::investigate() {
QHash<V8ScriptString, int> methodNames;
for (int idx = startIdx; idx < num; ++idx) {
QMetaMethod method = metaObject->method(idx);
qDebug(scriptengine) << "Investigate: " << metaObject->className() << " Method: " << method.name();
// perhaps keep this comment? Calls (like AudioScriptingInterface::playSound) seem to expect non-public methods to be script-accessible
/* if (method.access() != QMetaMethod::Public) continue;*/
@ -281,7 +295,7 @@ void ScriptObjectV8Proxy::investigate() {
v8::Local<v8::Object> v8Object = objectTemplate->NewInstance(_engine->getContext()).ToLocalChecked();
v8Object->SetAlignedPointerInInternalField(0, const_cast<void*>(internalPointsToQObjectProxy));
v8Object->SetAlignedPointerInInternalField(1, reinterpret_cast<void*>(this));
_v8Object.Reset(_engine->getIsolate(), v8Object);
}
@ -295,24 +309,31 @@ QString ScriptObjectV8Proxy::name() const {
}
ScriptObjectV8Proxy::QueryFlags ScriptObjectV8Proxy::queryProperty(const V8ScriptValue& object, const V8ScriptString& name, QueryFlags flags, uint* id) {
// check for properties
for (PropertyDefMap::const_iterator trans = _props.cbegin(); trans != _props.cend(); ++trans) {
const PropertyDef& propDef = trans.value();
if (propDef.name.constGet() != name.constGet()) continue;
*id = trans.key() | PROPERTY_TYPE;
return flags & (HandlesReadAccess | HandlesWriteAccess);
}
v8::HandleScope handleScope(_engine->getIsolate());
// V8TODO: this might be inefficient when there's large number of properties
v8::Local<v8::Context> context = _engine->getContext();
v8::String::Utf8Value nameStr(_engine->getIsolate(), name.constGet());
// check for methods
for (MethodDefMap::const_iterator trans = _methods.cbegin(); trans != _methods.cend(); ++trans) {
if (trans.value().name.constGet() != name.constGet()) continue;
v8::String::Utf8Value methodNameStr(_engine->getIsolate(), trans.value().name.constGet());
qDebug(scriptengine) << "queryProperty : " << *nameStr << " method: " << *methodNameStr;
if (!(trans.value().name == name)) continue;
*id = trans.key() | METHOD_TYPE;
return flags & (HandlesReadAccess | HandlesWriteAccess);
}
// check for properties
for (PropertyDefMap::const_iterator trans = _props.cbegin(); trans != _props.cend(); ++trans) {
const PropertyDef& propDef = trans.value();
if (!(propDef.name == name)) continue;
*id = trans.key() | PROPERTY_TYPE;
return flags & (HandlesReadAccess | HandlesWriteAccess);
}
// check for signals
for (SignalDefMap::const_iterator trans = _signals.cbegin(); trans != _signals.cend(); ++trans) {
if (trans.value().name.constGet() != name.constGet()) continue;
if (!(trans.value().name == name)) continue;
*id = trans.key() | SIGNAL_TYPE;
return flags & (HandlesReadAccess | HandlesWriteAccess);
}
@ -348,17 +369,51 @@ ScriptValue::PropertyFlags ScriptObjectV8Proxy::propertyFlags(const V8ScriptValu
}
void ScriptObjectV8Proxy::v8Get(v8::Local<v8::Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) {
// V8TODO
//info.GetReturnValue().Set();
v8::HandleScope handleScope(info.GetIsolate());
v8::String::Utf8Value utf8Value(info.GetIsolate(), name);
qDebug(scriptengine) << "Get: " << *utf8Value;
V8ScriptValue object(info.GetIsolate(), info.This());
ScriptObjectV8Proxy *proxy = ScriptObjectV8Proxy::unwrapProxy(object);
if (!proxy) {
qDebug(scriptengine) << "Proxy object not found when getting: " << *utf8Value;
return;
}
V8ScriptString nameString(info.GetIsolate(), v8::Local<v8::String>::Cast(name));
uint id;
QueryFlags flags = proxy->queryProperty(object, nameString, HandlesReadAccess, &id);
if (flags) {
V8ScriptValue value = proxy->property(object, nameString, id);
info.GetReturnValue().Set(value.get());
} else {
qDebug(scriptengine) << "Value not found: " << *utf8Value;
}
}
void ScriptObjectV8Proxy::v8Set(v8::Local<v8::Name> name, v8::Local<v8::Value> value, const v8::PropertyCallbackInfo<v8::Value>& info) {
// V8TODO
info.GetReturnValue().Set(value);
v8::HandleScope handleScope(info.GetIsolate());
v8::String::Utf8Value utf8Value(info.GetIsolate(), name);
qDebug(scriptengine) << "Set: " << *utf8Value;
V8ScriptValue object(info.GetIsolate(), info.This());
ScriptObjectV8Proxy *proxy = ScriptObjectV8Proxy::unwrapProxy(object);
if (!proxy) {
qDebug(scriptengine) << "Proxy object not found when setting: " << *utf8Value;
return;
}
V8ScriptString nameString(info.GetIsolate(), v8::Local<v8::String>::Cast(name));
//V8ScriptString nameString(info.GetIsolate(), name->ToString(proxy->_engine->getContext()).ToLocalChecked());
uint id;
QueryFlags flags = proxy->queryProperty(object, nameString, HandlesWriteAccess, &id);
if (flags) {
proxy->setProperty(object, nameString, id, V8ScriptValue(info.GetIsolate(), value));
info.GetReturnValue().Set(value);
} else {
qDebug(scriptengine) << "Value not found: " << *utf8Value;
}
}
V8ScriptValue ScriptObjectV8Proxy::property(const V8ScriptValue& object, const V8ScriptString& name, uint id) {
v8::HandleScope handleScope(_engine->getIsolate());
QObject* qobject = _object;
if (!qobject) {
_engine->getIsolate()->ThrowError("Referencing deleted native object");
@ -390,7 +445,7 @@ V8ScriptValue ScriptObjectV8Proxy::property(const V8ScriptValue& object, const V
if((*iter).returnType() == QMetaType::UnknownType) {
qDebug(scriptengine) << "Method with QMetaType::UnknownType " << metaObject->className() << " " << (*iter).name();
}
}
} //V8TODO: is new method created during every call? It needs to be cached instead
return ScriptMethodV8Proxy::newMethod(_engine, qobject, object, methodDef.methods, methodDef.numMaxParms);
}
case SIGNAL_TYPE: {
@ -417,6 +472,7 @@ V8ScriptValue ScriptObjectV8Proxy::property(const V8ScriptValue& object, const V
}
void ScriptObjectV8Proxy::setProperty(V8ScriptValue& object, const V8ScriptString& name, uint id, const V8ScriptValue& value) {
v8::HandleScope handleScope(_engine->getIsolate());
if (!(id & PROPERTY_TYPE)) return;
QObject* qobject = _object;
if (!qobject) {
@ -483,7 +539,7 @@ ScriptVariantV8Proxy* ScriptVariantV8Proxy::unwrapProxy(const V8ScriptValue& val
if (v8Object->InternalFieldCount() != 2) {
return nullptr;
}
if (v8Object->GetAlignedPointerFromInternalField(0) == internalPointsToQVariantProxy) {
if (v8Object->GetAlignedPointerFromInternalField(0) != internalPointsToQVariantProxy) {
return nullptr;
}
return reinterpret_cast<ScriptVariantV8Proxy*>(v8Object->GetAlignedPointerFromInternalField(1));
@ -495,8 +551,13 @@ QVariant ScriptVariantV8Proxy::unwrap(const V8ScriptValue& val) {
}
ScriptMethodV8Proxy::ScriptMethodV8Proxy(ScriptEngineV8* engine, QObject* object, V8ScriptValue lifetime,
const QList<QMetaMethod>& metas, int numMaxParms) :
_numMaxParms(numMaxParms), _engine(engine), _object(object), _objectLifetime(lifetime), _metas(metas) {
const QList<QMetaMethod>& metas, int numMaxParams) :
_numMaxParams(numMaxParams), _engine(engine), _object(object), _objectLifetime(lifetime), _metas(metas) {
}
ScriptMethodV8Proxy::~ScriptMethodV8Proxy() {
qDebug(scriptengine) << "ScriptMethodV8Proxy destroyed";
printf("ScriptMethodV8Proxy destroyed");
}
V8ScriptValue ScriptMethodV8Proxy::newMethod(ScriptEngineV8* engine, QObject* object, V8ScriptValue lifetime,
@ -534,23 +595,25 @@ QString ScriptMethodV8Proxy::fullName() const {
}*/
void ScriptMethodV8Proxy::callback(const v8::FunctionCallbackInfo<v8::Value>& arguments) {
if (!arguments.This()->IsObject()) {
v8::HandleScope handleScope(arguments.GetIsolate());
if (!arguments.Data()->IsObject()) {
arguments.GetIsolate()->ThrowError("Method value is not an object");
return;
}
if (!arguments.This()->IsCallable()) {
v8::Local<v8::Object> data = v8::Local<v8::Object>::Cast(arguments.Data());
/*if (!arguments.Data()->IsCallable()) {
arguments.GetIsolate()->ThrowError("Method value is not callable");
return;
}
if (arguments.This()->InternalFieldCount() != 2) {
}*/
if (data->InternalFieldCount() != 2) {
arguments.GetIsolate()->ThrowError("Incorrect number of internal fields during method call");
return;
}
if (arguments.This()->GetAlignedPointerFromInternalField(0) == internalPointsToMethodProxy) {
if (data->GetAlignedPointerFromInternalField(0) != internalPointsToMethodProxy) {
arguments.GetIsolate()->ThrowError("Internal field 0 of ScriptMethodV8Proxy V8 object has wrong value");
return;
}
ScriptMethodV8Proxy *proxy = reinterpret_cast<ScriptMethodV8Proxy*>(arguments.This()->GetAlignedPointerFromInternalField(1));
ScriptMethodV8Proxy *proxy = reinterpret_cast<ScriptMethodV8Proxy*>(data->GetAlignedPointerFromInternalField(1));
proxy->call(arguments);
}
@ -565,7 +628,7 @@ void ScriptMethodV8Proxy::call(const v8::FunctionCallbackInfo<v8::Value>& argume
v8::HandleScope handleScope(_engine->getIsolate());
int scriptNumArgs = arguments.Length();
int numArgs = std::min(scriptNumArgs, _numMaxParms);
int numArgs = std::min(scriptNumArgs, _numMaxParams);
const int scriptValueTypeId = qMetaTypeId<ScriptValue>();
@ -739,7 +802,7 @@ void ScriptMethodV8Proxy::call(const v8::FunctionCallbackInfo<v8::Value>& argume
}
int scriptNumArgs = context->argumentCount();
int numArgs = std::min(scriptNumArgs, _numMaxParms);
int numArgs = std::min(scriptNumArgs, _numMaxParams);
const int scriptValueTypeId = qMetaTypeId<ScriptValue>();

View file

@ -114,6 +114,7 @@ private: // storage
v8::UniquePersistent<v8::ObjectTemplate> _v8ObjectTemplate;
// V8TODO Maybe it doesn't really need to point to itsef?
v8::UniquePersistent<v8::Object> _v8Object;
int pointerCorruptionTest = 12345678;
Q_DISABLE_COPY(ScriptObjectV8Proxy)
};
@ -167,7 +168,8 @@ private: // storage
class ScriptMethodV8Proxy final {
public: // construction
ScriptMethodV8Proxy(ScriptEngineV8* engine, QObject* object, V8ScriptValue lifetime,
const QList<QMetaMethod>& metas, int numMaxParms);
const QList<QMetaMethod>& metas, int numMaxParams);
virtual ~ScriptMethodV8Proxy();
public: // QScriptClass implementation
virtual QString name() const { return fullName(); }
@ -182,7 +184,7 @@ private:
QString fullName() const;
private: // storage
const int _numMaxParms;
const int _numMaxParams;
ScriptEngineV8* _engine;
QPointer<QObject> _object;
V8ScriptValue _objectLifetime;