This commit is contained in:
Paul Knopf 2018-08-04 14:35:54 -04:00
commit a9cd59ff24
9 changed files with 149 additions and 27 deletions

View file

@ -191,3 +191,4 @@ ApplicationWindow {
- [ ] .NET Events to signals
- [ ] Custom V8 type that looks like an array, but wraps a .NET ```IList<T>``` instance, for modification of list in Qml, and performance.
- [ ] General perf improvements (particularly with reflection).
- [ ] Qml debugger for VS and VS Code.

View file

@ -31,26 +31,32 @@ void NetObject::method_gccollect(const BuiltinFunction *, Scope &scope, CallData
void NetObject::method_await(const BuiltinFunction *, Scope &scope, CallData *callData) {
scope.result = QV4::Encode::undefined();
if (callData->argc != 2) {
if (callData->argc != 2 && callData->argc != 3) {
qWarning() << "Invalid number of parameters passed to Net.await(task, callback)";
return;
}
ScopedValue task(scope, callData->args[0]);
ScopedValue callback(scope, callData->args[1]);
ScopedValue successCallback(scope, callData->args[1]);
ScopedValue failureCallback(scope);
if(callData->argc == 3) {
failureCallback = ScopedValue(scope, callData->args[2]);
}
if(task->isNullOrUndefined()) {
qWarning() << "No task for Net.await(task, callback)";
return;
}
if(callback->isNullOrUndefined()) {
if(successCallback->isNullOrUndefined()) {
qWarning() << "No callback for Net.await(task, callback)";
return;
}
QJSValue taskJsValue(scope.engine, task->asReturnedValue());
QJSValue callbackJsValue(scope.engine, callback->asReturnedValue());
QJSValue callbackJsValue(scope.engine, successCallback->asReturnedValue());
QJSValue failureJsValue(scope.engine, failureCallback->asReturnedValue());
QObject* qObject = taskJsValue.toQObject();
NetValueInterface* netValue = qobject_cast<NetValueInterface*>(qObject);
@ -61,7 +67,11 @@ void NetObject::method_await(const BuiltinFunction *, Scope &scope, CallData *ca
}
// Send the method to .NET, await the task, and call the callback.
awaitTask(netValue->getNetReference(), QSharedPointer<NetJSValue>(new NetJSValue(callbackJsValue)));
awaitTask(netValue->getNetReference(),
QSharedPointer<NetJSValue>(new NetJSValue(callbackJsValue)),
failureJsValue.isNull()
? QSharedPointer<NetJSValue>(nullptr)
: QSharedPointer<NetJSValue>(new NetJSValue(failureJsValue)));
}
void NetObject::method_cancelTokenSource(const BuiltinFunction *, Scope &scope, CallData *callData) {
@ -98,32 +108,49 @@ ReturnedValue NetObject::method_gccollect(const FunctionObject *b, const Value *
ReturnedValue NetObject::method_await(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) {
QV4::Scope scope(b);
if(argc != 2) {
if(argc != 2 && argc != 3) {
qWarning() << "Invalid number of parameters passed to Net.await(task, callback)";
return Encode::undefined();
}
QV4::ScopedValue task(scope, argv[0]);
QV4::ScopedValue callback(scope, argv[1]);
QV4::ScopedValue successCallback(scope, argv[1]);
QV4::ScopedValue failureCallback(scope, Encode::null());
if(argc == 3) {
// We are passing in a failure callback
failureCallback = argv[2];
}
if(task->isNullOrUndefined()) {
qWarning() << "No task for Net.await(task, callback)";
return Encode::undefined();
}
if(callback->isNullOrUndefined()) {
if(successCallback->isNullOrUndefined()) {
qWarning() << "No callback for Net.await(task, callback)";
return Encode::undefined();
}
QJSValue taskJsValue(scope.engine, task->asReturnedValue());
QJSValue callbackJsValue(scope.engine, callback->asReturnedValue());
QJSValue successCallbackJsValue(scope.engine, successCallback->asReturnedValue());
QJSValue failureCallbackJsValue(scope.engine, failureCallback->asReturnedValue());
if(!taskJsValue.isQObject()) {
qWarning() << "Invalid task object passed to Net.await(task, callback)";
return Encode::undefined();
}
if(!successCallbackJsValue.isCallable()) {
qWarning() << "Invalid function passed for success callback";
return Encode::undefined();
}
if(!failureCallbackJsValue.isNull() && !failureCallbackJsValue.isCallable()) {
qWarning() << "Invalid function passed for failure callback";
return Encode::undefined();
}
QObject* qObject = taskJsValue.toQObject();
NetValueInterface* netValue = qobject_cast<NetValueInterface*>(qObject);
@ -133,7 +160,11 @@ ReturnedValue NetObject::method_await(const FunctionObject *b, const Value *this
}
// Send the method to .NET, await the task, and call the callback.
awaitTask(netValue->getNetReference(), QSharedPointer<NetJSValue>(new NetJSValue(callbackJsValue)));
awaitTask(netValue->getNetReference(),
QSharedPointer<NetJSValue>(new NetJSValue(successCallbackJsValue)),
failureCallbackJsValue.isNull()
? QSharedPointer<NetJSValue>(nullptr)
: QSharedPointer<NetJSValue>(new NetJSValue(failureCallbackJsValue)));
return Encode::undefined();
}

View file

@ -11,7 +11,7 @@ typedef void (*writePropertyCb)(NetPropertyInfoContainer* property, NetReference
typedef void (*invokeMethodCb)(NetMethodInfoContainer* method, NetReferenceContainer* target, NetVariantListContainer* parameters, NetVariantContainer* result);
typedef void (*gcCollectCb)(int maxGeneration);
typedef bool (*raiseNetSignalsCb)(NetReferenceContainer* target, LPWCSTR signalName, NetVariantListContainer* parameters);
typedef void (*awaitTaskCb)(NetReferenceContainer* target, NetJSValueContainer* callback);
typedef void (*awaitTaskCb)(NetReferenceContainer* target, NetJSValueContainer* successCallback, NetJSValueContainer* failureCallback);
struct Q_DECL_EXPORT NetTypeInfoManagerCallbacks {
isTypeValidCb isTypeValid;
@ -126,10 +126,14 @@ bool raiseNetSignals(QSharedPointer<NetReference> target, QString signalName, QS
return sharedCallbacks.raiseNetSignals(targetContainer, (LPWCSTR)signalName.utf16(), parametersContainer);
}
void awaitTask(QSharedPointer<NetReference> target, QSharedPointer<NetJSValue> callback) {
void awaitTask(QSharedPointer<NetReference> target, QSharedPointer<NetJSValue> successCallback, QSharedPointer<NetJSValue> failureCallback) {
NetReferenceContainer* targetContainer = new NetReferenceContainer{target};
NetJSValueContainer* callbackContainer = new NetJSValueContainer{callback};
sharedCallbacks.awaitTask(targetContainer, callbackContainer);
NetJSValueContainer* sucessCallbackContainer = new NetJSValueContainer{successCallback};
NetJSValueContainer* failureCallbackContainer = nullptr;
if(failureCallback != nullptr) {
failureCallbackContainer = new NetJSValueContainer{failureCallback};
}
sharedCallbacks.awaitTask(targetContainer, sucessCallbackContainer, failureCallbackContainer);
}
extern "C" {

View file

@ -33,6 +33,6 @@ void gcCollect(int generation);
bool raiseNetSignals(QSharedPointer<NetReference> target, QString signalName, QSharedPointer<NetVariantList> parameters);
void awaitTask(QSharedPointer<NetReference> target, QSharedPointer<NetJSValue> callback);
void awaitTask(QSharedPointer<NetReference> target, QSharedPointer<NetJSValue> successCallback, QSharedPointer<NetJSValue> failureCallback);
#endif // NET_TYPE_INFO_MANAGER_H

View file

@ -1,4 +1,5 @@
using System.Threading;
using System;
using System.Threading;
using System.Threading.Tasks;
using Moq;
using Qml.Net.Internal.Qml;
@ -99,6 +100,45 @@ namespace Qml.Net.Tests.Qml
}
}
[Fact]
public void Can_listen_to_exception_throw()
{
var oldContext = SynchronizationContext.Current;
SynchronizationContext.SetSynchronizationContext(new NoAsyncSynchronizationContext());
try
{
Mock.Setup(x => x.TestAsync()).Returns(Task.FromException<Exception>(new Exception("throw this")));
Mock.Setup(x => x.TestMethodWithArg("throw this"));
Mock.Setup(x => x.TestMethodWithArg("success"));
NetTestHelper.RunQml(qmlApplicationEngine,
@"
import QtQuick 2.0
import tests 1.0
AwaitTestsQml {
id: test
Component.onCompleted: function() {
var task = test.testAsync('throw this')
Net.await(task, function(result) {
test.testMethodWithArg('success')
},
function(ex) {
test.testMethodWithArg(ex.message)
})
}
}
");
Mock.Verify(x => x.TestMethodWithArg("throw this"), Times.Once);
Mock.Verify(x => x.TestMethodWithArg("success"), Times.Never);
}
finally
{
SynchronizationContext.SetSynchronizationContext(oldContext);
}
}
private class NoAsyncSynchronizationContext : SynchronizationContext
{
public override void Post(SendOrPostCallback d, object state)

View file

@ -343,26 +343,45 @@ namespace Qml.Net.Internal
}
}
public async Task AwaitTask(IntPtr t, IntPtr c)
public async Task AwaitTask(IntPtr t, IntPtr sc, IntPtr fc)
{
using (var target = new NetReference(t))
using(var callback = new NetJsValue(c))
using(var successCallback = new NetJsValue(sc))
using(var failureCallback = fc != IntPtr.Zero ? new NetJsValue(fc) : null)
{
var taskObject = target.Instance;
if (taskObject is Task task)
{
await task;
try
{
await task;
}
catch (Exception ex)
{
// The task throw an exception.
// Invoke our failure callback.
if (failureCallback == null)
{
// The caller didn't want to listen to failures.
// Just throw it and let "TaskScheduler.UnobservedTaskException"
// handle it.
throw;
}
failureCallback.Call(ex);
return;
}
try
{
var result = (object)((dynamic)task).Result;
callback.Call(result);
successCallback.Call(result);
}
catch (RuntimeBinderException)
{
// No "Result" property, a task with no callbacks.
// TODO: Find a better way to handle this than catching an exception.
callback.Call();
successCallback.Call();
}
}
else

View file

@ -8,7 +8,14 @@ namespace Qml.Net.Internal
{
public static bool IsPrimitive(Type type)
{
if (type.Namespace == "System") return true;
if (type.Namespace == "System")
{
if (type.Name == "Exception")
{
return false;
}
return true;
}
return false;
}

View file

@ -73,7 +73,7 @@ namespace Qml.Net.Internal.Types
bool RaiseNetSignals(IntPtr target, string signalName, IntPtr parameters);
Task AwaitTask(IntPtr target, IntPtr callback);
Task AwaitTask(IntPtr target, IntPtr succesCallback, IntPtr failureCallback);
}
internal class CallbacksImpl
@ -126,7 +126,7 @@ namespace Qml.Net.Internal.Types
delegate bool RaiseNetSignalsDelegate(IntPtr target, [MarshalAs(UnmanagedType.LPWStr)]string signalName, IntPtr parameters);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
delegate void AwaitTaskDelegate(IntPtr target, IntPtr callback);
delegate void AwaitTaskDelegate(IntPtr target, IntPtr successCallback, IntPtr failureCallback);
public CallbacksImpl(ICallbacks callbacks)
{
@ -226,9 +226,9 @@ namespace Qml.Net.Internal.Types
return _callbacks.RaiseNetSignals(target, signalName, parameters);
}
private void AwaitTask(IntPtr target, IntPtr callback)
private void AwaitTask(IntPtr target, IntPtr succesCallback, IntPtr failureCallback)
{
_callbacks.AwaitTask(target, callback);
_callbacks.AwaitTask(target, succesCallback, failureCallback);
}
public Callbacks Callbacks()

View file

@ -20,5 +20,25 @@ namespace Qml.Net
}
return Activator.CreateInstance(type);
}
public static ITypeCreator FromDelegate(Func<Type, object> func)
{
return new DelegateTypeCreator(func);
}
class DelegateTypeCreator : ITypeCreator
{
private readonly Func<Type, object> _func;
public DelegateTypeCreator(Func<Type, object> func)
{
_func = func;
}
public object Create(Type type)
{
return _func(type);
}
}
}
}