Skip to content

Commit

Permalink
Added Promise handling and a really basic event loop
Browse files Browse the repository at this point in the history
  • Loading branch information
kfarnung committed Jul 13, 2017
1 parent 42d4bfc commit 2ac571d
Show file tree
Hide file tree
Showing 9 changed files with 312 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using ChakraCoreHost.Tasks;
using ChakraHost.Hosting;

namespace ChakraHost
{
public static class Program
{
private static ManualResetEvent queueEvent = new ManualResetEvent(false);
private static volatile int outstandingItems = 0;
private static Queue<TaskItem> taskQueue = new Queue<TaskItem>();
private static object taskSync = new object();

private struct CommandLineArguments
{
public int ArgumentsStart;
Expand All @@ -17,6 +26,61 @@ private struct CommandLineArguments
// delegates aren't collected while the script is running.
private static readonly JavaScriptNativeFunction echoDelegate = Echo;
private static readonly JavaScriptNativeFunction runScriptDelegate = RunScript;
private static readonly JavaScriptNativeFunction doSuccessfulWorkDelegate = DoSuccessfulWork;
private static readonly JavaScriptNativeFunction doUnsuccessfulWorkDelegate = DoUnsuccessfulWork;

private static void Enqueue(TaskItem item)
{
lock(taskSync)
{
taskQueue.Enqueue(item);
}

queueEvent.Set();
}

private static TaskItem Dequeue()
{
TaskItem item = null;
lock (taskSync)
{
item = taskQueue.Dequeue();
}

queueEvent.Reset();

return item;
}

private static void PumpMessages()
{
while (true)
{
bool hasTasks = false;
bool hasOutstandingItems = false;

lock (taskSync)
{
hasTasks = taskQueue.Count > 0;
hasOutstandingItems = outstandingItems > 0;
}

if (hasTasks)
{
TaskItem task = Dequeue();
task.Run();
task.Dispose();
}
else if (hasOutstandingItems)
{
queueEvent.WaitOne();
}
else
{
break;
}
}
}

private static void ThrowException(string errorString)
{
Expand Down Expand Up @@ -75,6 +139,75 @@ private static JavaScriptValue RunScript(JavaScriptValue callee, bool isConstruc
return JavaScriptContext.RunScript(script, currentSourceContext++, filename);
}

private static async void CallAfterDelay(JavaScriptValue function, int delayInMilliseconds, string value)
{
lock (taskSync)
{
outstandingItems++;
}

await Task.Delay(delayInMilliseconds);

Enqueue(new ActionTaskItem(() => { function.CallFunction(JavaScriptValue.GlobalObject, JavaScriptValue.FromString(value)); }));

lock (taskSync)
{
outstandingItems--;
}
}

private static JavaScriptValue DoSuccessfulWork(JavaScriptValue callee, bool isConstructCall, JavaScriptValue[] arguments, ushort argumentCount, IntPtr callbackData)
{
JavaScriptValue resolve;
JavaScriptValue reject;
JavaScriptValue promise = JavaScriptValue.CreatePromise(out resolve, out reject);

CallAfterDelay(resolve, 5000, "promise from native code");

return promise;
}

private static JavaScriptValue DoUnsuccessfulWork(JavaScriptValue callee, bool isConstructCall, JavaScriptValue[] arguments, ushort argumentCount, IntPtr callbackData)
{
JavaScriptValue resolve;
JavaScriptValue reject;
JavaScriptValue promise = JavaScriptValue.CreatePromise(out resolve, out reject);

CallAfterDelay(reject, 5000, "promise from native code");

return promise;
}

private static JavaScriptValue ResolveCallback(JavaScriptValue callee, bool isConstructCall, JavaScriptValue[] arguments, ushort argumentCount, IntPtr callbackData)
{
if (argumentCount > 1)
{
Console.WriteLine("Resolved: " + arguments[1].ConvertToString().ToString());
}

return JavaScriptValue.Invalid;
}

private static JavaScriptValue RejectCallback(JavaScriptValue callee, bool isConstructCall, JavaScriptValue[] arguments, ushort argumentCount, IntPtr callbackData)
{
if (argumentCount > 1)
{
Console.WriteLine("Rejected: " + arguments[1].ConvertToString().ToString());
}

return JavaScriptValue.Invalid;
}

private static void ContinuePromise(JavaScriptValue promise)
{
JavaScriptPropertyId thenId = JavaScriptPropertyId.FromString("then");
JavaScriptValue thenFunction = promise.GetProperty(thenId);

JavaScriptValue resolveFunc = JavaScriptValue.CreateFunction(ResolveCallback);
JavaScriptValue rejectFunc = JavaScriptValue.CreateFunction(RejectCallback);
JavaScriptValue thenPromise = thenFunction.CallFunction(promise, resolveFunc, rejectFunc);
}

private static void DefineHostCallback(JavaScriptValue globalObject, string callbackName, JavaScriptNativeFunction callback, IntPtr callbackData)
{
//
Expand Down Expand Up @@ -141,6 +274,8 @@ private static JavaScriptContext CreateHostContext(JavaScriptRuntime runtime, st

DefineHostCallback(hostObject, "echo", echoDelegate, IntPtr.Zero);
DefineHostCallback(hostObject, "runScript", runScriptDelegate, IntPtr.Zero);
DefineHostCallback(hostObject, "doSuccessfulWork", doSuccessfulWorkDelegate, IntPtr.Zero);
DefineHostCallback(hostObject, "doUnsuccessfulWork", doUnsuccessfulWorkDelegate, IntPtr.Zero);

//
// Create an array for arguments.
Expand Down Expand Up @@ -198,6 +333,11 @@ private static void PrintScriptException(JavaScriptValue exception)
Console.Error.WriteLine("chakrahost: exception: {0}", message);
}

private static void PromiseContinuationCallback(JavaScriptValue task, IntPtr callbackState)
{
taskQueue.Enqueue(new JsTaskItem(task));
}

//
// The main entry point for the host.
//
Expand Down Expand Up @@ -234,6 +374,8 @@ public static int Main(string[] arguments)

using (new JavaScriptContext.Scope(context))
{
JavaScriptRuntime.SetPromiseContinuationCallback(PromiseContinuationCallback, IntPtr.Zero);

//
// Load the script from the disk.
//
Expand All @@ -248,6 +390,25 @@ public static int Main(string[] arguments)
try
{
result = JavaScriptContext.RunScript(script, currentSourceContext++, arguments[commandLineArguments.ArgumentsStart]);

// Call JS functions and continue the returned promises.
JavaScriptPropertyId hostId = JavaScriptPropertyId.FromString("host");
JavaScriptValue hostObject = JavaScriptValue.GlobalObject.GetProperty(hostId);

JavaScriptPropertyId doSuccessfulJsWorkId = JavaScriptPropertyId.FromString("doSuccessfulJsWork");
JavaScriptValue doSuccessfulJsWorkFunction = hostObject.GetProperty(doSuccessfulJsWorkId);
JavaScriptValue resolvedPromise = doSuccessfulJsWorkFunction.CallFunction(JavaScriptValue.GlobalObject);

ContinuePromise(resolvedPromise);

JavaScriptPropertyId doUnsuccessfulJsWorkId = JavaScriptPropertyId.FromString("doUnsuccessfulJsWork");
JavaScriptValue doUnsuccessfulJsWorkFunction = hostObject.GetProperty(doUnsuccessfulJsWorkId);
JavaScriptValue rejectedPromise = doUnsuccessfulJsWorkFunction.CallFunction(JavaScriptValue.GlobalObject);

ContinuePromise(rejectedPromise);

// Pump messages in the task queue.
PumpMessages();
}
catch (JavaScriptScriptException e)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,11 +113,18 @@
<Compile Include="Hosting\JavaScriptValueType.cs" />
<Compile Include="Hosting\Native.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Tasks\ActionTaskItem.cs" />
<Compile Include="Tasks\JsTaskItem.cs" />
<Compile Include="Tasks\TaskItem.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<ItemGroup />
<ItemGroup>
<Content Include="test.js">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,11 @@ public static JavaScriptRuntime Create()
return Create(JavaScriptRuntimeAttributes.None, JavaScriptRuntimeVersion.Version11, null);
}

public static void SetPromiseContinuationCallback(JavaScriptPromiseContinuationCallback callback, IntPtr callbackState)
{
Native.JsSetPromiseContinuationCallback(callback, callbackState);
}

/// <summary>
/// Disposes a runtime.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,13 @@ public static JavaScriptValue CreateArray(uint length)
return reference;
}

public static JavaScriptValue CreatePromise(out JavaScriptValue resolve, out JavaScriptValue reject)
{
JavaScriptValue promise;
Native.ThrowIfError(Native.JsCreatePromise(out promise, out resolve, out reject));
return promise;
}

/// <summary>
/// Creates a new JavaScript error object
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -439,5 +439,8 @@ internal static extern JavaScriptErrorCode JsParseSerializedScriptWithCallback(J
[DllImport(DllName)]
internal static extern JavaScriptErrorCode JsRunSerializedScriptWithCallback(JavaScriptSerializedScriptLoadSourceCallback scriptLoadCallback,
JavaScriptSerializedScriptUnloadCallback scriptUnloadCallback, byte[] buffer, JavaScriptSourceContext sourceContext, string sourceUrl, out JavaScriptValue result);

[DllImport(DllName)]
internal static extern JavaScriptErrorCode JsCreatePromise(out JavaScriptValue promise, out JavaScriptValue resolve, out JavaScriptValue reject);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ChakraCoreHost.Tasks
{
public class ActionTaskItem : TaskItem
{
private Action action;

public ActionTaskItem(Action action)
{
this.action = action;
}

public override void Run()
{
this.action.Invoke();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using ChakraHost.Hosting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ChakraCoreHost.Tasks
{
public class JsTaskItem : TaskItem
{
private JavaScriptValue taskFunc;

public JsTaskItem(JavaScriptValue taskFunc)
{
this.taskFunc = taskFunc;

// We need to keep the object alive in the Chakra GC.
this.taskFunc.AddRef();
}

~JsTaskItem()
{
this.Dispose(false);
}

public override void Run()
{
taskFunc.CallFunction(JavaScriptValue.GlobalObject);
}

protected override void Dispose(bool disposing)
{
// We need to release the object so that the Chakra GC can reclaim it.
taskFunc.Release();

base.Dispose(disposing);

if (disposing)
{
// No need to finalize the object if we've been disposed.
GC.SuppressFinalize(this);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ChakraCoreHost.Tasks
{
public abstract class TaskItem : IDisposable
{
public abstract void Run();

protected virtual void Dispose(bool disposing)
{
}

// This code added to correctly implement the disposable pattern.
public void Dispose()
{
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Dispose(true);
}
}
}
Loading

0 comments on commit 2ac571d

Please sign in to comment.