From ef9021150881868edf3c1fe8b35188e67ca68bae Mon Sep 17 00:00:00 2001 From: Karolis2011 Date: Fri, 5 Feb 2021 15:14:33 +0200 Subject: [PATCH 1/4] Fixes type mapper again --- ByondLang/ChakraCore/TypeMapper.cs | 90 +++++++++++++++--------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/ByondLang/ChakraCore/TypeMapper.cs b/ByondLang/ChakraCore/TypeMapper.cs index 9859a1e..3895c0a 100644 --- a/ByondLang/ChakraCore/TypeMapper.cs +++ b/ByondLang/ChakraCore/TypeMapper.cs @@ -357,8 +357,8 @@ private void ProjectProperties(EmbeddedItem externalItem, EmbeddingObjectOptions if (property.GetGetMethod() != null) { - JsValue nativeGetFunction(JsValue callee, bool isConstructCall, JsValue[] args, ushort argCount, IntPtr callbackData) - { + JsNativeFunction nativeGetFunction = (callee, isConstructCall, args, argCount, callbackData) => + { if (instance && obj == null) { CreateAndSetError($"Invalid context for '{propertyName}' property."); @@ -407,8 +407,8 @@ JsValue nativeGetFunction(JsValue callee, bool isConstructCall, JsValue[] args, if (property.GetSetMethod() != null) { - JsValue nativeSetFunction(JsValue callee, bool isConstructCall, JsValue[] args, ushort argCount, IntPtr callbackData) - { + JsNativeFunction nativeSetFunction = (callee, isConstructCall, args, argCount, callbackData) => + { JsValue undefinedValue = JsValue.Undefined; if (instance && obj == null) @@ -479,54 +479,54 @@ private void ProjectMethods(EmbeddedItem externalItem, EmbeddingObjectOptions op string methodName = methodGroup.Key; MethodInfo[] methodCandidates = methodGroup.Select(m => m.Info).ToArray(); - JsValue nativeFunction(JsValue callee, bool isConstructCall, JsValue[] args, ushort argCount, IntPtr callbackData) - { - if (instance && obj == null) - { - CreateAndSetError($"Invalid context while calling method '{methodName}'."); - return JsValue.Undefined; - } + JsNativeFunction nativeFunction = (callee, isConstructCall, args, argCount, callbackData) => + { + if (instance && obj == null) + { + CreateAndSetError($"Invalid context while calling method '{methodName}'."); + return JsValue.Undefined; + } - if (!SelectAndProcessFunction(methodCandidates, args, argCount, out MethodInfo bestSelection, out object[] processedArgs)) - { - CreateAndSetError($"Suitable method '{methodName}' was not found."); - return JsValue.Undefined; - } + if (!SelectAndProcessFunction(methodCandidates, args, argCount, out MethodInfo bestSelection, out object[] processedArgs)) + { + CreateAndSetError($"Suitable method '{methodName}' was not found."); + return JsValue.Undefined; + } - object result; + object result; - try - { - result = bestSelection.Invoke(obj, processedArgs); - } - catch (Exception e) - { - Exception exception = UnwrapException(e); - var wrapperException = exception as JsException; - JsValue errorValue; + try + { + result = bestSelection.Invoke(obj, processedArgs); + } + catch (Exception e) + { + Exception exception = UnwrapException(e); + var wrapperException = exception as JsException; + JsValue errorValue; - if (wrapperException != null) - { - errorValue = CreateErrorFromWrapperException(wrapperException); - } - else - { - string errorMessage = instance ? - $"Host method '{methodName}' invocation error: {exception.Message}" - : - $"Host static type '{typeName}' method '{methodName}' invocation error: {exception.Message}" - ; - errorValue = JsValue.CreateError(JsValue.FromString(errorMessage)); - } - JsContext.SetException(errorValue); + if (wrapperException != null) + { + errorValue = CreateErrorFromWrapperException(wrapperException); + } + else + { + string errorMessage = instance ? + $"Host method '{methodName}' invocation error: {exception.Message}" + : + $"Host static type '{typeName}' method '{methodName}' invocation error: {exception.Message}" + ; + errorValue = JsValue.CreateError(JsValue.FromString(errorMessage)); + } + JsContext.SetException(errorValue); - return JsValue.Undefined; - } + return JsValue.Undefined; + } - JsValue resultValue = MapToScriptType(result); + JsValue resultValue = MapToScriptType(result); - return resultValue; - } + return resultValue; + }; nativeFunctions.Add(nativeFunction); JsValue methodValue = JsValue.CreateNamedFunction(methodName, nativeFunction); From 1170a980165acbe26c8a650d5cd2d50ca7144af8 Mon Sep 17 00:00:00 2001 From: Karolis2011 Date: Fri, 5 Feb 2021 15:48:14 +0200 Subject: [PATCH 2/4] New UI and fina fixes --- ByondLang/ChakraCore/TypeMapper.cs | 138 ++++++++++---------- ByondLang/wwwroot/head_script.js | 98 -------------- ByondLang/wwwroot/index.htm | 111 ++++++++++++++++ ByondLang/wwwroot/index.html | 68 ---------- ByondLang/wwwroot/js/app.js | 203 +++++++++++++++++++++++++++++ ByondLang/wwwroot/script.js | 59 --------- ByondLang/wwwroot/style.css | 18 --- 7 files changed, 383 insertions(+), 312 deletions(-) delete mode 100644 ByondLang/wwwroot/head_script.js create mode 100644 ByondLang/wwwroot/index.htm delete mode 100644 ByondLang/wwwroot/index.html create mode 100644 ByondLang/wwwroot/js/app.js delete mode 100644 ByondLang/wwwroot/script.js delete mode 100644 ByondLang/wwwroot/style.css diff --git a/ByondLang/ChakraCore/TypeMapper.cs b/ByondLang/ChakraCore/TypeMapper.cs index 3895c0a..66e1f40 100644 --- a/ByondLang/ChakraCore/TypeMapper.cs +++ b/ByondLang/ChakraCore/TypeMapper.cs @@ -359,46 +359,46 @@ private void ProjectProperties(EmbeddedItem externalItem, EmbeddingObjectOptions { JsNativeFunction nativeGetFunction = (callee, isConstructCall, args, argCount, callbackData) => { - if (instance && obj == null) - { - CreateAndSetError($"Invalid context for '{propertyName}' property."); - return JsValue.Undefined; - } + if (instance && obj == null) + { + CreateAndSetError($"Invalid context for '{propertyName}' property."); + return JsValue.Undefined; + } - object result; + object result; - try - { - result = property.GetValue(obj, new object[0]); - } - catch (Exception e) - { - Exception exception = UnwrapException(e); - var wrapperException = exception as JsException; - JsValue errorValue; + try + { + result = property.GetValue(obj, new object[0]); + } + catch (Exception e) + { + Exception exception = UnwrapException(e); + var wrapperException = exception as JsException; + JsValue errorValue; - if (wrapperException != null) - { - errorValue = CreateErrorFromWrapperException(wrapperException); - } - else - { - string errorMessage = instance ? - $"Property '{propertyName}' get operation failed: {exception.Message}" - : - $"Property '{propertyName}' of static type '{typeName}' get operation failed: {exception.Message}" - ; - errorValue = JsValue.CreateError(JsValue.FromString(errorMessage)); - } - JsContext.SetException(errorValue); + if (wrapperException != null) + { + errorValue = CreateErrorFromWrapperException(wrapperException); + } + else + { + string errorMessage = instance ? + $"Property '{propertyName}' get operation failed: {exception.Message}" + : + $"Property '{propertyName}' of static type '{typeName}' get operation failed: {exception.Message}" + ; + errorValue = JsValue.CreateError(JsValue.FromString(errorMessage)); + } + JsContext.SetException(errorValue); - return JsValue.Undefined; - } + return JsValue.Undefined; + } - JsValue resultValue = MapToScriptType(result); + JsValue resultValue = MapToScriptType(result); - return resultValue; - } + return resultValue; + }; nativeFunctions.Add(nativeGetFunction); JsValue getMethodValue = JsValue.CreateFunction(nativeGetFunction); @@ -409,47 +409,47 @@ private void ProjectProperties(EmbeddedItem externalItem, EmbeddingObjectOptions { JsNativeFunction nativeSetFunction = (callee, isConstructCall, args, argCount, callbackData) => { - JsValue undefinedValue = JsValue.Undefined; + JsValue undefinedValue = JsValue.Undefined; - if (instance && obj == null) - { - CreateAndSetError($"Invalid context for '{propertyName}' property."); - return undefinedValue; - } + if (instance && obj == null) + { + CreateAndSetError($"Invalid context for '{propertyName}' property."); + return undefinedValue; + } - object value = MapToHostType(args[1]); - ReflectionHelpers.FixPropertyValueType(ref value, property); + object value = MapToHostType(args[1]); + ReflectionHelpers.FixPropertyValueType(ref value, property); - try - { - property.SetValue(obj, value, new object[0]); - } - catch (Exception e) - { - Exception exception = UnwrapException(e); - var wrapperException = exception as JsException; - JsValue errorValue; + try + { + property.SetValue(obj, value, new object[0]); + } + catch (Exception e) + { + Exception exception = UnwrapException(e); + var wrapperException = exception as JsException; + JsValue errorValue; - if (wrapperException != null) - { - errorValue = CreateErrorFromWrapperException(wrapperException); - } - else - { - string errorMessage = instance ? - $"Host object property '{propertyName}' setting failed: {exception.Message}" - : - $"Host type '{typeName}' property '{propertyName}' setting failed: {exception.Message}" - ; - errorValue = JsValue.CreateError(JsValue.FromString(errorMessage)); - } - JsContext.SetException(errorValue); + if (wrapperException != null) + { + errorValue = CreateErrorFromWrapperException(wrapperException); + } + else + { + string errorMessage = instance ? + $"Host object property '{propertyName}' setting failed: {exception.Message}" + : + $"Host type '{typeName}' property '{propertyName}' setting failed: {exception.Message}" + ; + errorValue = JsValue.CreateError(JsValue.FromString(errorMessage)); + } + JsContext.SetException(errorValue); - return undefinedValue; - } + return undefinedValue; + } - return undefinedValue; - } + return undefinedValue; + }; nativeFunctions.Add(nativeSetFunction); JsValue setMethodValue = JsValue.CreateFunction(nativeSetFunction); diff --git a/ByondLang/wwwroot/head_script.js b/ByondLang/wwwroot/head_script.js deleted file mode 100644 index e3eaa9b..0000000 --- a/ByondLang/wwwroot/head_script.js +++ /dev/null @@ -1,98 +0,0 @@ -var programId = null; -window.vueapp = {}; -function console_log(o) { - if (typeof o != "string") o = JSON.stringify(o); - $("#output").append(`[${Date()}] ${o}\r\n`); -} - -function create_program_com() { - if (programId != null) return; - $.ajax({ - type: "GET", - url: "/new_program", - data: { - type: "Computer", - }, - success: (data) => { - console_log("Program id: " + data); - programId = data; - get_buffer(); - }, - }); -} - -function create_program_tcom() { - if (programId != null) return; - $.ajax({ - type: "GET", - url: "/new_program", - data: { - type: "TCom", - }, - success: (data) => { - console_log("Program id: " + data); - programId = data; - }, - }); -} - -function exec_program() { - if (programId == null) return; - $.ajax({ - type: "GET", - url: `/execute`, - data: { - id: programId, - code: editor.getValue(), - }, - success: (data) => { - console_log(data); - get_buffer() - }, - }); -} - -function remove_program() { - if (programId == null) return; - $.ajax({ - type: "GET", - url: `/remove`, - data: { - id: programId, - }, - success: (data) => { - window.vueapp.$data.buffer = ""; - $("#output").text(""); - console_log("Program removed. " + data) - programId = null; - }, - }); -} - -function get_buffer() { - if (programId == null) return; - $.ajax({ - type: "GET", - url: `/computer/get_buffer`, - data: { - id: programId, - }, - success: (data) => { - window.vueapp.$data.buffer = data; - }, - }); -} - -function reset() { - if (programId == null) return; - $.ajax({ - type: "GET", - url: `/clear`, - success: (data) => { - window.vueapp.$data.buffer = ""; - $("#output").text(""); - programId = null; - console_log(data) - }, - }); -} diff --git a/ByondLang/wwwroot/index.htm b/ByondLang/wwwroot/index.htm new file mode 100644 index 0000000..01c8b3f --- /dev/null +++ b/ByondLang/wwwroot/index.htm @@ -0,0 +1,111 @@ + + + + NTSL2++ DM-less emulator. + + + + + + + +
+ + + +
+ + {{ alert.message }} + + + + + + Computer program + + + + + + Reset daemon + +
+ +
+

Options:

+ +
+
+ + +
+ +
+
+
+
+ + + + + + + + + + diff --git a/ByondLang/wwwroot/index.html b/ByondLang/wwwroot/index.html deleted file mode 100644 index e3332e0..0000000 --- a/ByondLang/wwwroot/index.html +++ /dev/null @@ -1,68 +0,0 @@ - - - - - NTSL2++ DM-less emulator. - - - - - - - - - - - - -
-
Term.write("AAAA")
-if(5 * 3)
-  Term.write(2^8)
-
-
-
-
- - - - - - - - - diff --git a/ByondLang/wwwroot/js/app.js b/ByondLang/wwwroot/js/app.js new file mode 100644 index 0000000..53871cd --- /dev/null +++ b/ByondLang/wwwroot/js/app.js @@ -0,0 +1,203 @@ +window.ace.config.set( + "basePath", + "https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.11/" +); +window.ace.require("ace/ext/language_tools"); + + +Vue.component('Editor', { + template: '
', + props: ['editorId', 'value'], + data () { + return { + editor: Object, + beforeContent: '' + } + }, + watch: { + 'value' (value) { + if (this.beforeContent !== value) { + this.editor.setValue(value, 1) + } + } + }, + mounted () { + + this.editor = window.ace.edit(this.$refs.editor) + this.editor.setValue(this.value, 1) + + this.editor.getSession().setMode(`ace/mode/javascript`) + this.editor.setTheme(`ace/theme/monokai`) + + this.editor.on('change', () => { + this.beforeContent = this.editor.getValue() + this.$emit('input', this.beforeContent) + }) + } +}) +const co = { + props: ["fg", "bg"], + computed: { + style() { + return { + color: "#" + this.fg, + "background-color": "#" + this.bg, + }; + }, + }, + template: '', +}; +const to = { + props: ["to"], + methods: { + invoke() { + if (programId == null) return; + var topic = this.to; + var data = ""; + if (this.to[0] == "?") { + topic = this.to.substring(1); + data = prompt("Please enter input...", ""); + } + $.ajax({ + type: "GET", + url: `/computer/topic`, + data: { + id: programId, + topic: topic, + data: data, + }, + success: (data) => { + get_buffer(); + }, + }); + }, + }, + template: + '', +}; + + +const scriptPresets = { + Computer: `Term.write("AAAA") +if(5 * 3) + Term.write(2^8)` +} + +var app = new Vue({ + el: "#app", + data: { + programType: null, + programId: null, + script: '', + console_buffer: null, + console_buffer_auto_update: true, + alert: { + type: '', + message: null, + tid: null + } + }, + computed: { + term() { + if (this.console_buffer == null) return {} + return { + components: { + co, + to, + }, + template: `
${this.console_buffer}
`, + }; + }, + }, + methods: { + a(type = '', message = null) { + this.alert.type = type + this.alert.message = message + if(type) { + if(this.alert.tid) { + clearTimeout(this.alert.tid) + } + this.alert.tid = setTimeout(() => this.a(), 3000) + } + }, + createProgram(type = 'Computer') { + axios.get("/new_program", { + params: { + type + } + }).then(r => { + this.programId = r.data + this.programType = type + this.loadPreset(type) + this.a('success', `Created program with id (${this.programId}).`) + }) + }, + loadPreset(type = 'Computer') { + this.script = scriptPresets[type] || 'debugger;' + }, + execute() { + if (this.programId == null) return + axios.get("/execute", { + params: { + id: this.programId, + code: this.script + } + }).then(r => { + if(r.data) + return + this.a('success', 'Executed script.') + }) + .catch(() => { + this.a('error', 'Script execution failed.') + }) + }, + update_console_buffer() { + axios.get("/computer/get_buffer", { + params: { + id: this.programId + } + }).then(r => { + this.console_buffer = r.data + }) + .catch(() => { + this.a('error', 'Failed to update console buffer.') + }) + }, + remove() { + if (this.programId == null) return + axios.get("/remove", { + params: { + id: this.programId + } + }).then(r => { + console.log(r); + this.programId = null + this.programType = null + this.console_buffer = null + this.a('success', 'Program successfully disposed of.') + }) + .catch(() => { + this.a('error', 'There was a problem while removing program.') + }) + }, + reset() { + axios.get("/clear").then(() => { + this.programId = null + this.programType = null + this.console_buffer = null + this.a('success', 'Daemon state successfully cleared.') + }) + .catch(() => { + this.a('error', 'There was a problem while resting daemon state.') + }) + }, + fire() { + if(this.programType == 'Computer' && this.console_buffer_auto_update) { + this.update_console_buffer() + } + } + }, + vuetify: new Vuetify(), +}); + +setInterval(app.fire, 1000); \ No newline at end of file diff --git a/ByondLang/wwwroot/script.js b/ByondLang/wwwroot/script.js deleted file mode 100644 index 4994867..0000000 --- a/ByondLang/wwwroot/script.js +++ /dev/null @@ -1,59 +0,0 @@ -const co = { - props: ["fg", "bg"], - computed: { - style() { - return { - color: "#" + this.fg, - "background-color": "#" + this.bg, - }; - }, - }, - template: '', -}; -const to = { - props: ["to"], - methods: { - invoke() { - if (programId == null) return; - var topic = this.to; - var data = ""; - if (this.to[0] == "?") { - topic = this.to.substring(1); - data = prompt("Please enter input...", ""); - } - $.ajax({ - type: "GET", - url: `/computer/topic`, - data: { - id: programId, - topic: topic, - data: data, - }, - success: (data) => { - get_buffer(); - }, - }); - }, - }, - template: - '', -}; - -window.vueapp = new Vue({ - el: "#consolebuffer", - template: '', - data: { - buffer: "", - }, - computed: { - term() { - return { - components: { - co, - to, - }, - template: `
${this.buffer}
`, - }; - }, - }, -}); diff --git a/ByondLang/wwwroot/style.css b/ByondLang/wwwroot/style.css deleted file mode 100644 index 297acbf..0000000 --- a/ByondLang/wwwroot/style.css +++ /dev/null @@ -1,18 +0,0 @@ -#consolebuffer, -#output { - white-space: pre-wrap; - font-family: Consolas, monaco, monospace; -} - -#source { - width: 100%; - height: 25em; -} - -.terminal { - width: 100%; - font-family: monospace; - line-height: 1em; - font-size: 1em; - text-align: center; -} From ba717b13ca042ca67e1ac9674ad965f16e7b07ab Mon Sep 17 00:00:00 2001 From: Karolis2011 Date: Fri, 5 Feb 2021 18:03:48 +0200 Subject: [PATCH 3/4] Refactors out Runtime class and beggining of debugger support --- ByondLang.Api/Protos/Program.proto | 5 + .../ChakraCore/Hosting/JsDiagDebugEvent.cs | 37 ++++ .../Hosting/JsDiagDebugEventCallback.cs | 15 ++ ByondLang/ChakraCore/Hosting/Native.cs | 92 +++++++++ ByondLang/ChakraCore/JsScheduler.cs | 17 +- ByondLang/ChakraCore/JsTask.cs | 17 +- ByondLang/Interface/BaseProgram.cs | 151 +++++++++++++-- ByondLang/Interface/ComputerProgram.cs | 10 +- ByondLang/Interface/Runtime.cs | 183 ------------------ ByondLang/Interface/TestProgram.cs | 2 +- ByondLang/Services/NTSL3StateService.cs | 5 +- ByondLang/Services/ProgramService.cs | 14 +- ByondLang/State/IExecutionContext.cs | 1 + ByondLang/State/LocalExecutionContext.cs | 8 + ByondLang/State/RemoteExecutionContext.cs | 14 +- .../StateTests/RuntimeContextTests.cs | 9 +- ByondLang/wwwroot/index.htm | 3 + ByondLang/wwwroot/js/app.js | 5 +- 18 files changed, 354 insertions(+), 234 deletions(-) create mode 100644 ByondLang/ChakraCore/Hosting/JsDiagDebugEvent.cs create mode 100644 ByondLang/ChakraCore/Hosting/JsDiagDebugEventCallback.cs delete mode 100644 ByondLang/Interface/Runtime.cs diff --git a/ByondLang.Api/Protos/Program.proto b/ByondLang.Api/Protos/Program.proto index 20ee9f9..7f434bf 100644 --- a/ByondLang.Api/Protos/Program.proto +++ b/ByondLang.Api/Protos/Program.proto @@ -36,9 +36,14 @@ message TerminalState { string buffer = 1; } +message DebugingState { + bool enabled = 1; +} + service Program { rpc recycle (VoidMessage) returns (VoidMessage); rpc status (StatusRequest) returns (StatusResponse); rpc execute (ExecuteRequest) returns (VoidMessage); rpc handleTopic (TopicRequest) returns (VoidMessage); + rpc setDebugingState (DebugingState) returns (VoidMessage); } \ No newline at end of file diff --git a/ByondLang/ChakraCore/Hosting/JsDiagDebugEvent.cs b/ByondLang/ChakraCore/Hosting/JsDiagDebugEvent.cs new file mode 100644 index 0000000..9e3259a --- /dev/null +++ b/ByondLang/ChakraCore/Hosting/JsDiagDebugEvent.cs @@ -0,0 +1,37 @@ +namespace ByondLang.ChakraCore.Hosting +{ + /// + /// The type of a typed JavaScript array. + /// + public enum JsDiagDebugEvent + { + /// + /// Indicates a new script being compiled, this includes script, eval, new function. + /// + SourceCompile = 0, + /// + /// Indicates compile error for a script. + /// + CompileError, + /// + /// Indicates a break due to a breakpoint. + /// + Breakpoint, + /// + /// Indicates a break after completion of step action. + /// + StepComplete, + /// + /// Indicates a break due to debugger statement. + /// + DebuggerStatement, + /// + /// Indicates a break due to async break. + /// + AsyncBreak, + /// + /// Indicates a break due to a runtime script exception. + /// + RuntimeException + }; +} \ No newline at end of file diff --git a/ByondLang/ChakraCore/Hosting/JsDiagDebugEventCallback.cs b/ByondLang/ChakraCore/Hosting/JsDiagDebugEventCallback.cs new file mode 100644 index 0000000..1a4300d --- /dev/null +++ b/ByondLang/ChakraCore/Hosting/JsDiagDebugEventCallback.cs @@ -0,0 +1,15 @@ +using System; + +namespace ByondLang.ChakraCore.Hosting +{ + /// + /// User implemented callback routine for debug events. + /// + /// + /// Use JsDiagStartDebugging to register the callback. + /// + /// The type of JsDiagDebugEvent event. + /// Additional data related to the debug event. + /// The state passed to JsDiagStartDebugging. + public delegate void JsDiagDebugEventCallback(JsDiagDebugEvent debugEvent, JsValue eventData, IntPtr callbackState); +} \ No newline at end of file diff --git a/ByondLang/ChakraCore/Hosting/Native.cs b/ByondLang/ChakraCore/Hosting/Native.cs index 3954ec4..0597069 100644 --- a/ByondLang/ChakraCore/Hosting/Native.cs +++ b/ByondLang/ChakraCore/Hosting/Native.cs @@ -437,8 +437,100 @@ internal static extern JsErrorCode JsSetHostPromiseRejectionTracker( [DllImport(DllName, CharSet = CharSet.Unicode)] internal static extern JsErrorCode JsRun(JsValue script, JsSourceContext sourceContext, JsValue sourceUrl, JsParseScriptAttributes parseAttributes, out JsValue result); + + /// + /// Starts debugging in the given runtime. + /// + /// Runtime to put into debug mode. + /// Registers a callback to be called on every JsDiagDebugEvent. + /// User provided state that will be passed back to the callback. + /// + /// The code JsNoError if the operation succeeded, a failure code otherwise. + /// + /// + /// The runtime should be active on the current thread and should not be in debug state. + /// + [DllImport(DllName)] + internal static extern JsErrorCode JsDiagStartDebugging(JsRuntime runtime, JsDiagDebugEventCallback debugEventCallback, IntPtr callbackState); + + /// + /// Stops debugging in the given runtime. + /// + /// Runtime to stop debugging. + /// User provided state that was passed in JsDiagStartDebugging. + /// + /// The code JsNoError if the operation succeeded, a failure code otherwise. + /// + /// + /// The runtime should be active on the current thread and in debug state. + /// + [DllImport(DllName)] + internal static extern JsErrorCode JsDiagStopDebugging(JsRuntime runtime, out IntPtr callbackState); + + /// + /// Request the runtime to break on next JavaScript statement. + /// + /// Runtime to request break. + /// + /// The code JsNoError if the operation succeeded, a failure code otherwise. + /// + /// + /// The runtime should be in debug state. This API can be called from another runtime. + /// + [DllImport(DllName)] + internal static extern JsErrorCode JsDiagRequestAsyncBreak(JsRuntime runtime); + + /// + /// List all breakpoints in the current runtime. + /// + /// Array of breakpoints. + /// + /// + /// [{ + /// "breakpointId" : 1, + /// "scriptId" : 1, + /// "line" : 0, + /// "column" : 62 + /// }] + /// + /// + /// + /// The code JsNoError if the operation succeeded, a failure code otherwise. + /// + /// + /// The current runtime should be in debug state. This API can be called when runtime is at a break or running. + /// + [DllImport(DllName)] + internal static extern JsErrorCode JsDiagGetBreakpoints(out JsValue breakpoints); + + /// + /// Sets breakpoint in the specified script at give location. + /// + /// Id of script from JsDiagGetScripts or JsDiagGetSource to put breakpoint. + /// 0 based line number to put breakpoint. + /// 0 based column number to put breakpoint. + /// Breakpoint object with id, line and column if success. + /// + /// + /// { + /// "breakpointId" : 1, + /// "line" : 2, + /// "column" : 4 + /// } + /// + /// + /// + /// The code JsNoError if the operation succeeded, a failure code otherwise. + /// + /// + /// The current runtime should be in debug state. This API can be called when runtime is at a break or running. + /// + [DllImport(DllName)] + internal static extern JsErrorCode JsDiagGetBreakpoints(uint scriptId, uint lineNumber, uint columnNumber, out JsValue breakpoint); + // TODO: // https://github.com/Microsoft/ChakraCore/issues/4324 + } } diff --git a/ByondLang/ChakraCore/JsScheduler.cs b/ByondLang/ChakraCore/JsScheduler.cs index c399ade..73ed3d1 100644 --- a/ByondLang/ChakraCore/JsScheduler.cs +++ b/ByondLang/ChakraCore/JsScheduler.cs @@ -8,11 +8,22 @@ namespace ByondLang.ChakraCore { public abstract class JsScheduler { + protected BaseProgram program; + public JsScheduler(BaseProgram program) + { + this.program = program; + } + public abstract void EnterBreakState(); public abstract void QueueTask(JsTask task); + public abstract void QueueTaskDebug(JsTask task); - public virtual JsTask Run(Func func, BaseProgram program = null, JsTaskPriority priority = JsTaskPriority.LOWEST) => - new JsTask(func, priority, program).Start(this); - public virtual JsTask Run(Action action, BaseProgram program = null, JsTaskPriority priority = JsTaskPriority.LOWEST) => new JsTask(action, priority, program).Start(this); + public virtual JsTask Run(Func func, JsTaskPriority priority = JsTaskPriority.LOWEST) => + new JsTask(func, priority).Start(this); + public virtual JsTask Run(Action action, JsTaskPriority priority = JsTaskPriority.LOWEST) => new JsTask(action, priority).Start(this); + + public virtual JsTask RunDebug(Func func, JsTaskPriority priority = JsTaskPriority.LOWEST) => + new JsTask(func, priority).StartDebug(this); + public virtual JsTask RunDebug(Action action, JsTaskPriority priority = JsTaskPriority.LOWEST) => new JsTask(action, priority).StartDebug(this); } } diff --git a/ByondLang/ChakraCore/JsTask.cs b/ByondLang/ChakraCore/JsTask.cs index 1dc29e4..02d5364 100644 --- a/ByondLang/ChakraCore/JsTask.cs +++ b/ByondLang/ChakraCore/JsTask.cs @@ -30,7 +30,6 @@ public class JsTask { public bool IsCompleted => State == JsTaskState.Failed || State == JsTaskState.Complete; public JsTaskPriority Priority { get; protected set; } - public BaseProgram Program { get; protected set; } public JsTaskState State { get; protected set; } = JsTaskState.Initialized; private Action m_action; @@ -38,10 +37,9 @@ public class JsTask protected JsTask() { } - public JsTask(Action function, JsTaskPriority priority = JsTaskPriority.LOWEST, BaseProgram program = null) + public JsTask(Action function, JsTaskPriority priority = JsTaskPriority.LOWEST) { m_action = function; - Program = program; Priority = priority; } @@ -78,6 +76,12 @@ public JsTask Start(JsScheduler scheduler) return this; } + public JsTask StartDebug(JsScheduler scheduler) + { + scheduler.QueueTaskDebug(this); + return this; + } + public JsTaskAwaiter GetAwaiter() { return new JsTaskAwaiter(this); @@ -103,7 +107,6 @@ public class JsTask: JsTask public JsTask(Func function, JsTaskPriority priority = JsTaskPriority.LOWEST, BaseProgram program = null) { m_action = function; - Program = program; Priority = priority; } @@ -151,6 +154,12 @@ public override void Run() return this; } + public new JsTask StartDebug(JsScheduler scheduler) + { + scheduler.QueueTaskDebug(this); + return this; + } + public new JsTaskAwaiter GetAwaiter() { return new JsTaskAwaiter(this); diff --git a/ByondLang/Interface/BaseProgram.cs b/ByondLang/Interface/BaseProgram.cs index ed24c3f..2b72c82 100644 --- a/ByondLang/Interface/BaseProgram.cs +++ b/ByondLang/Interface/BaseProgram.cs @@ -16,34 +16,57 @@ namespace ByondLang.Interface /// public class BaseProgram : IDisposable { + public const int DEFAULT_SCRIPT_TIMEOUT = 2000; + public const int DEFAULT_PROMISE_TIMEOUT = 2000; public const int CALLBACK_HASH_LEN = 12; + private static Random random = new Random(); - protected Runtime _runtime; - protected JsContext _context; - protected TypeMapper _typeMapper; + + protected JsRuntime runtime; + protected JsContext context; + + protected TypeMapper typeMapper = new TypeMapper(); + protected JsPFIFOScheduler scheduler = new JsPFIFOScheduler(); + protected Task lastExecutionTask; protected Dictionary> callbacks = new Dictionary>(); private ILogger logger; - private bool disposedValue; - public BaseProgram(Runtime runtime, JsContext context, TypeMapper typeMapper) + public bool IsInBreak { get; protected set; } + + public BaseProgram(IServiceProvider serviceProvider) { - context.AddRef(); - _runtime = runtime; - _context = context; - _typeMapper = typeMapper; - logger = runtime.serviceProvider?.GetService>(); + runtime = JsRuntime.Create(JsRuntimeAttributes.AllowScriptInterrupt); + + logger = serviceProvider?.GetService>(); } - public void InitializeState() + public async Task InitializeState() { - _runtime.Function(() => + await Function(() => { - using (new JsContext.Scope(_context)) + context = runtime.CreateContext(); + using (new JsContext.Scope(context)) { + JsContext.SetPromiseContinuationCallback(PromiseContinuationCallback, IntPtr.Zero); InstallInterfaces(); + context.AddRef(); + } + }, JsTaskPriority.INITIALIZATION); + } + + private void PromiseContinuationCallback(JsValue task, IntPtr callbackState) + { + task.AddRef(); + JsContext context = JsContext.Current; + TimedFunction(DEFAULT_PROMISE_TIMEOUT, () => + { + using (new JsContext.Scope(context)) + { + task.CallFunction(JsValue.GlobalObject); + task.Release(); } - }, this, priority: JsTaskPriority.INITIALIZATION); + }, priority: JsTaskPriority.PROMISE); } internal JsCallback RegisterCallback(JsValue callback) @@ -72,7 +95,7 @@ public virtual void InstallInterfaces() { // Add generic global APIs accessible from everywhere var glob = JsValue.GlobalObject; - glob.SetProperty("btoa", _typeMapper.MTS((Func)delegate (JsValue value) + glob.SetProperty("btoa", typeMapper.MTS((Func)delegate (JsValue value) { if(value.ValueType != JsValueType.String) { @@ -81,7 +104,7 @@ public virtual void InstallInterfaces() var plainTextBytes = Encoding.UTF8.GetBytes(value.ToString()); return Convert.ToBase64String(plainTextBytes); }), false); - glob.SetProperty("atob", _typeMapper.MTS((Func)delegate (JsValue value) + glob.SetProperty("atob", typeMapper.MTS((Func)delegate (JsValue value) { if (value.ValueType != JsValueType.String) { @@ -100,25 +123,30 @@ internal virtual bool HandleException(Exception exception) public JsTask ExecuteScript(string script) { - return _runtime.TimedFunction(() => + return TimedFunction(() => { - using (new JsContext.Scope(_context)) + using (new JsContext.Scope(context)) { return JsContext.RunScript(script); } - }, this, HandleException, JsTaskPriority.EXECUTION); + }, HandleException, JsTaskPriority.EXECUTION); } + + + #region IDisposable support + private bool disposedValue; protected virtual void Dispose(bool disposing) { if (!disposedValue) { if (disposing) { - _runtime.RemoveContext(this); callbacks.Clear(); + typeMapper.Dispose(); } - _context.Release(); + context.Release(); + runtime.Dispose(); disposedValue = true; } @@ -134,5 +162,86 @@ public void Dispose() Dispose(disposing: true); GC.SuppressFinalize(this); } + #endregion + + #region Timed Function implemetation base + private void TimedFn(int timeout, Action timedAction, Func exHandler) + { + using (var timer = new Timer(state => + { + runtime.Disabled = true; + }, null, timeout, Timeout.Infinite)) + { + try + { + timedAction(); + } + //catch (JsScriptException ex) + //{ + // if (ex.ErrorCode != JsErrorCode.ScriptTerminated) + // if (exHandler != null) + // exHandler(ex); + // else + // throw; + //} + catch (Exception ex) + { + if (exHandler == null || !exHandler(ex)) + throw; + } + } + runtime.Disabled = false; + } + + private R TimedFn(int timeout, Func timedAction, Func exHandler) + { + R result = default; + using (var timer = new Timer(state => + { + runtime.Disabled = true; + }, null, timeout, Timeout.Infinite)) + { + try + { + result = timedAction(); + } + //catch (JsScriptException ex) + //{ + // if (ex.ErrorCode != JsErrorCode.ScriptTerminated) + // if (exHandler != null) + // exHandler(ex); + // else + // throw; + //} + catch (Exception ex) + { + if (exHandler == null || !exHandler(ex)) + throw; + } + + } + runtime.Disabled = false; + return result; + } + #endregion + #region Timed Function via sheduler implemetations + public JsTask TimedFunction(int timeout, Action function, Func exHandler = null, JsTaskPriority priority = JsTaskPriority.LOWEST) => + scheduler.Run(() => TimedFn(timeout, function, exHandler), priority); + + public JsTask TimedFunction(Action function, Func exHandler = null, JsTaskPriority priority = JsTaskPriority.EXECUTION) => + TimedFunction(DEFAULT_SCRIPT_TIMEOUT, function, exHandler, priority); + + public JsTask Function(Action function, JsTaskPriority priority = JsTaskPriority.EXECUTION) => + scheduler.Run(function, priority); + + public JsTask TimedFunction(int timeout, Func function, Func exHandler = null, JsTaskPriority priority = JsTaskPriority.LOWEST) => + scheduler.Run(() => TimedFn(timeout, function, exHandler), priority); + + public JsTask TimedFunction(Func function, Func exHandler = null, JsTaskPriority priority = JsTaskPriority.EXECUTION) => + TimedFunction(DEFAULT_SCRIPT_TIMEOUT, function, exHandler, priority); + + public JsTask Function(Func function, JsTaskPriority priority = JsTaskPriority.EXECUTION) => + scheduler.Run(function, priority); + #endregion } } diff --git a/ByondLang/Interface/ComputerProgram.cs b/ByondLang/Interface/ComputerProgram.cs index 6787a6a..f10f206 100644 --- a/ByondLang/Interface/ComputerProgram.cs +++ b/ByondLang/Interface/ComputerProgram.cs @@ -13,7 +13,7 @@ public class ComputerProgram : BaseProgram internal Terminal terminal; public string ComputerRef { get; private set; } - public ComputerProgram(Runtime runtime, JsContext context, ChakraCore.TypeMapper typeMapper) : base(runtime, context, typeMapper) + public ComputerProgram(IServiceProvider serviceProvider) : base(serviceProvider) { terminal = new Terminal(this); } @@ -31,14 +31,14 @@ public void HandleTopic(string hash, string data) JsCallback callback; if (weakCallback.TryGetTarget(out callback)) { - _runtime.TimedFunction(() => + TimedFunction(() => { - using (new JsContext.Scope(_context)) + using (new JsContext.Scope(context)) { JsValue callbackParam = data == null ? JsValue.Null : JsValue.FromString(data); callback.CallbackFunction.CallFunction(JsValue.GlobalObject, callbackParam); } - }, this, HandleException, JsTaskPriority.CALLBACK); + }, HandleException, JsTaskPriority.CALLBACK); } } @@ -59,7 +59,7 @@ public override void InstallInterfaces() base.InstallInterfaces(); // Install APIs: term var glob = JsValue.GlobalObject; - glob.SetProperty("Term", _typeMapper.MTS(terminal), true); ; + glob.SetProperty("Term", typeMapper.MTS(terminal), true); ; } } } diff --git a/ByondLang/Interface/Runtime.cs b/ByondLang/Interface/Runtime.cs deleted file mode 100644 index 67122ef..0000000 --- a/ByondLang/Interface/Runtime.cs +++ /dev/null @@ -1,183 +0,0 @@ -using ByondLang.ChakraCore; -using ByondLang.ChakraCore.Hosting; -using ByondLang.Services; -using Microsoft.CSharp.RuntimeBinder; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace ByondLang.Interface -{ - public class Runtime : IDisposable - { - public const int DEFAULT_SCRIPT_TIMEOUT = 2000; - public const int DEFAULT_PROMISE_TIMEOUT = 2000; - private JsRuntime runtime; - private TypeMapper typeMapper = new TypeMapper(); - private JsPFIFOScheduler scheduler = new JsPFIFOScheduler(); - private List programs = new List(); - private Dictionary callbacks = new Dictionary(); - internal IServiceProvider serviceProvider; - - public Runtime(IServiceProvider serviceProvider) - { - runtime = JsRuntime.Create(JsRuntimeAttributes.AllowScriptInterrupt); - this.serviceProvider = serviceProvider; - } - - public async Task BuildContext(Func initializer) where T : BaseProgram - { - var buildTask = Function(() => - { - var context = runtime.CreateContext(); - using (new JsContext.Scope(context)) - { - // TODO: Configure promise callback. Promises should be added to be executed along main queue of work. - JsContext.SetPromiseContinuationCallback(PromiseContinuationCallback, IntPtr.Zero); - } - return context; - }, priority: JsTaskPriority.INITIALIZATION); - var context = await buildTask; - var program = initializer(this, context, typeMapper); - program.InitializeState(); - programs.Add(program); - return program; - } - - public void RemoveContext(BaseProgram context) - { - programs.Remove(context); - } - - private void PromiseContinuationCallback(JsValue task, IntPtr callbackState) - { - task.AddRef(); - JsContext context = JsContext.Current; - TimedFunction(DEFAULT_PROMISE_TIMEOUT, () => - { - using (new JsContext.Scope(context)) - { - task.CallFunction(JsValue.GlobalObject); - task.Release(); - } - }, priority: JsTaskPriority.PROMISE); - } - - private void TimedFn(int timeout, Action timedAction, Func exHandler) - { - using (var timer = new Timer(state => - { - runtime.Disabled = true; - }, null, timeout, Timeout.Infinite)) - { - try - { - timedAction(); - } - //catch (JsScriptException ex) - //{ - // if (ex.ErrorCode != JsErrorCode.ScriptTerminated) - // if (exHandler != null) - // exHandler(ex); - // else - // throw; - //} - catch (Exception ex) - { - if (exHandler == null || !exHandler(ex)) - throw; - } - } - runtime.Disabled = false; - } - - private R TimedFn(int timeout, Func timedAction, Func exHandler) - { - R result = default; - using (var timer = new Timer(state => - { - runtime.Disabled = true; - }, null, timeout, Timeout.Infinite)) - { - try - { - result = timedAction(); - } - //catch (JsScriptException ex) - //{ - // if (ex.ErrorCode != JsErrorCode.ScriptTerminated) - // if (exHandler != null) - // exHandler(ex); - // else - // throw; - //} - catch (Exception ex) - { - if (exHandler == null || !exHandler(ex)) - throw; - } - - } - runtime.Disabled = false; - return result; - } - - - public JsTask TimedFunction(int timeout, Action function, BaseProgram program = null, Func exHandler = null, JsTaskPriority priority = JsTaskPriority.LOWEST) => - scheduler.Run(() => TimedFn(timeout, function, exHandler), program, priority); - - public JsTask TimedFunction(Action function, BaseProgram program = null, Func exHandler = null, JsTaskPriority priority = JsTaskPriority.EXECUTION) => - TimedFunction(DEFAULT_SCRIPT_TIMEOUT, function, program, exHandler, priority); - - public JsTask Function(Action function, BaseProgram program = null, JsTaskPriority priority = JsTaskPriority.EXECUTION) => - scheduler.Run(function, program, priority); - - public JsTask TimedFunction(int timeout, Func function, BaseProgram program = null, Func exHandler = null, JsTaskPriority priority = JsTaskPriority.LOWEST) => - scheduler.Run(() => TimedFn(timeout, function, exHandler), program, priority); - - public JsTask TimedFunction(Func function, BaseProgram program = null, Func exHandler = null, JsTaskPriority priority = JsTaskPriority.EXECUTION) => - TimedFunction(DEFAULT_SCRIPT_TIMEOUT, function, program, exHandler, priority); - - public JsTask Function(Func function, BaseProgram program = null, JsTaskPriority priority = JsTaskPriority.EXECUTION) => - scheduler.Run(function, program, priority); - - #region IDisposable Support - private bool disposedValue = false; // To detect redundant calls - - protected virtual void Dispose(bool disposing) - { - if (!disposedValue) - { - if (disposing) - { - typeMapper.Dispose(); - } - var _programToDispose = programs.ToList(); - foreach (var program in _programToDispose) - { - program.Dispose(); - } - runtime.Dispose(); - - disposedValue = true; - } - } - - ~Runtime() - { - Dispose(false); - } - - // 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); - // TODO: uncomment the following line if the finalizer is overridden above. - GC.SuppressFinalize(this); - } - #endregion - } -} diff --git a/ByondLang/Interface/TestProgram.cs b/ByondLang/Interface/TestProgram.cs index 8688923..6ce5442 100644 --- a/ByondLang/Interface/TestProgram.cs +++ b/ByondLang/Interface/TestProgram.cs @@ -10,7 +10,7 @@ public class TestProgram : BaseProgram { public bool HasErrored { get; private set; } = false; - public TestProgram(Runtime runtime, JsContext context, ChakraCore.TypeMapper typeMapper) : base(runtime, context, typeMapper) + public TestProgram(IServiceProvider serviceProvider) : base(serviceProvider) { } diff --git a/ByondLang/Services/NTSL3StateService.cs b/ByondLang/Services/NTSL3StateService.cs index f2fb72d..f3802fa 100644 --- a/ByondLang/Services/NTSL3StateService.cs +++ b/ByondLang/Services/NTSL3StateService.cs @@ -11,17 +11,16 @@ public class NTSL3StateService: IDisposable { public ProgramType type = ProgramType.None; public BaseProgram program = null; - public Runtime runtime; + public IServiceProvider serviceProvider = null; public NTSL3StateService(IServiceProvider serviceProvider) { - runtime = new Runtime(serviceProvider); + this.serviceProvider = serviceProvider; } public void Dispose() { program?.Dispose(); - runtime.Dispose(); } } } diff --git a/ByondLang/Services/ProgramService.cs b/ByondLang/Services/ProgramService.cs index ecf1d79..e8163c0 100644 --- a/ByondLang/Services/ProgramService.cs +++ b/ByondLang/Services/ProgramService.cs @@ -18,11 +18,6 @@ public ProgramService(NTSL3StateService stateService) _state = stateService; } - internal async Task NewProgram(Func initializer) where T : BaseProgram - { - _state.program = await _state.runtime.BuildContext(initializer); - } - public override async Task status(StatusRequest request, ServerCallContext context) { if(_state.program == null) @@ -34,7 +29,8 @@ public override async Task status(StatusRequest request, ServerC case ProgramType.None: throw new Exception("Unspecified program type"); case ProgramType.ComputerProgram: - await NewProgram((r, c, tm) => new ComputerProgram(r, c, tm)); + _state.program = new ComputerProgram(_state.serviceProvider); + await _state.program.InitializeState(); break; default: break; @@ -85,6 +81,12 @@ public override Task recycle(VoidMessage request, ServerCallContext return Task.FromResult(request); } + public override Task setDebugingState(DebugingState request, ServerCallContext context) + { + + return Task.FromResult(new VoidMessage()); + } + public void FullDispose() { _state.Dispose(); diff --git a/ByondLang/State/IExecutionContext.cs b/ByondLang/State/IExecutionContext.cs index a7bc0e5..b8a35ff 100644 --- a/ByondLang/State/IExecutionContext.cs +++ b/ByondLang/State/IExecutionContext.cs @@ -13,5 +13,6 @@ public interface IExecutionContext : IDisposable void Start(Func portGenerator, IServiceProvider serviceProvider); Task HandleTopic(string topic, string data); Task Recycle(); + Task SetDebuggingState(bool state); } } diff --git a/ByondLang/State/LocalExecutionContext.cs b/ByondLang/State/LocalExecutionContext.cs index 4a5da53..fbc6d5d 100644 --- a/ByondLang/State/LocalExecutionContext.cs +++ b/ByondLang/State/LocalExecutionContext.cs @@ -61,6 +61,14 @@ public Task Recycle() return Task.FromResult(true); } + public async Task SetDebuggingState(bool state) + { + await programService.setDebugingState(new DebugingState() + { + Enabled = state + }, null); + } + public void Start(Func portGenerator, IServiceProvider serviceProvider) { programService = new Services.ProgramService(new Services.NTSL3StateService(serviceProvider)); diff --git a/ByondLang/State/RemoteExecutionContext.cs b/ByondLang/State/RemoteExecutionContext.cs index 7b0bdac..80a3d85 100644 --- a/ByondLang/State/RemoteExecutionContext.cs +++ b/ByondLang/State/RemoteExecutionContext.cs @@ -6,6 +6,7 @@ using System.IO; using Grpc.Net.Client; using System.Net.Http; +using ByondLang.Api; namespace ByondLang.State { @@ -90,7 +91,7 @@ private void spawn(int port) // Computer only public async Task HandleTopic(string topic, string data) { - await client.handleTopicAsync(new Api.TopicRequest() { + await client.handleTopicAsync(new TopicRequest() { TopicId = topic, Data = data });; @@ -100,7 +101,7 @@ public async Task Recycle() { try { - await client.recycleAsync(new Api.VoidMessage()); + await client.recycleAsync(new VoidMessage()); lastStatus = null; return true; } @@ -110,5 +111,14 @@ public async Task Recycle() } } + + public async Task SetDebuggingState(bool state) + { + await client.setDebugingStateAsync(new DebugingState() + { + Enabled = state + }); + lastStatus = null; + } } } diff --git a/ByondLang/UnitTests/StateTests/RuntimeContextTests.cs b/ByondLang/UnitTests/StateTests/RuntimeContextTests.cs index 3d2aa84..41f211d 100644 --- a/ByondLang/UnitTests/StateTests/RuntimeContextTests.cs +++ b/ByondLang/UnitTests/StateTests/RuntimeContextTests.cs @@ -12,13 +12,12 @@ public class RuntimeContextTests [Fact] public async void ConstructAndDispose() { - using (var runtime = new Runtime(null)) + using (var program = new BaseProgram(null)) { - using (var program = await runtime.BuildContext((r, c, m) => new BaseProgram(r, c, m))) { - var result = program.ExecuteScript("Math.PI * (5+2)"); + await program.InitializeState(); + var result = program.ExecuteScript("Math.PI * (5+2)"); - Assert.True(result.Wait(200)); - } + Assert.True(result.Wait(200)); } } } diff --git a/ByondLang/wwwroot/index.htm b/ByondLang/wwwroot/index.htm index 01c8b3f..fc28403 100644 --- a/ByondLang/wwwroot/index.htm +++ b/ByondLang/wwwroot/index.htm @@ -106,6 +106,9 @@

Options:

integrity="sha256-KFXf4u8zJlXEUJckmmZbITzYENXMuqPqyrlscCKeqFU=" crossorigin="anonymous" > + diff --git a/ByondLang/wwwroot/js/app.js b/ByondLang/wwwroot/js/app.js index 53871cd..7fb09e4 100644 --- a/ByondLang/wwwroot/js/app.js +++ b/ByondLang/wwwroot/js/app.js @@ -76,6 +76,9 @@ const to = { '', }; +Vue.component('debugger', { + +}) const scriptPresets = { Computer: `Term.write("AAAA") @@ -143,7 +146,7 @@ var app = new Vue({ code: this.script } }).then(r => { - if(r.data) + if(!r.data) return this.a('success', 'Executed script.') }) From ea9acbe5ba9a0dc3acbf26b0733289acac14c3e0 Mon Sep 17 00:00:00 2001 From: Karolis2011 Date: Sun, 7 Feb 2021 18:50:46 +0200 Subject: [PATCH 4/4] *sigh* --- ByondInterpretedLanguage.sln | 6 - ByondLang/ByondLang.csproj | 5 - .../JsDiagBreakOnExceptionAttributes.cs | 21 + .../ChakraCore/Hosting/JsDiagDebugEvent.cs | 2 +- .../ChakraCore/Hosting/JsDiagStepType.cs | 33 ++ ByondLang/ChakraCore/Hosting/JsRuntime.cs | 30 ++ ByondLang/ChakraCore/Hosting/JsValue.cs | 4 +- ByondLang/ChakraCore/Hosting/Native.cs | 243 +++++++++++ ByondLang/ChakraCore/JsPFIFOScheduler.cs | 216 +++++---- ByondLang/ChakraCore/JsScheduler.cs | 68 +-- ByondLang/ChakraCore/JsTask.cs | 410 +++++++++--------- ByondLang/ChakraCore/JsTaskTimed.cs | 99 +++++ ByondLang/ChakraCore/TypeMapper.cs | 95 ++-- ByondLang/Controllers/NTSLController.cs | 54 ++- ByondLang/Interface/BaseProgram.cs | 156 ++++--- ByondLang/Interface/ComputerProgram.cs | 6 +- ByondLang/Interface/JsCallback.cs | 9 +- .../Interface/StateObjects/Communications.cs | 50 +-- ByondLang/Interface/StateObjects/Terminal.cs | 51 ++- ByondLang/Models/Event.cs | 25 ++ ByondLang/Models/EventDebugBreak.cs | 20 + ByondLang/Models/EventType.cs | 8 + ByondLang/Program.cs | 25 -- ByondLang/Services/NTSL3Service.cs | 63 +-- ByondLang/Services/NTSL3StateService.cs | 26 -- ByondLang/Services/ProgramService.cs | 96 ---- ByondLang/State/IExecutionContext.cs | 18 - ByondLang/State/LocalExecutionContext.cs | 77 ---- ByondLang/State/RemoteExecutionContext.cs | 124 ------ ByondLang/UnitTests/ChakraCore/TMTests.cs | 42 +- .../StateTests/RuntimeContextTests.cs | 10 +- .../StateTests/RuntimeTaskSchedulerTests.cs | 24 + ByondLang/WorkerStartup.cs | 42 -- ByondLang/wwwroot/index.htm | 101 +++-- ByondLang/wwwroot/js/app.js | 33 +- 35 files changed, 1221 insertions(+), 1071 deletions(-) create mode 100644 ByondLang/ChakraCore/Hosting/JsDiagBreakOnExceptionAttributes.cs create mode 100644 ByondLang/ChakraCore/Hosting/JsDiagStepType.cs create mode 100644 ByondLang/ChakraCore/JsTaskTimed.cs create mode 100644 ByondLang/Models/Event.cs create mode 100644 ByondLang/Models/EventDebugBreak.cs create mode 100644 ByondLang/Models/EventType.cs delete mode 100644 ByondLang/Services/NTSL3StateService.cs delete mode 100644 ByondLang/Services/ProgramService.cs delete mode 100644 ByondLang/State/IExecutionContext.cs delete mode 100644 ByondLang/State/LocalExecutionContext.cs delete mode 100644 ByondLang/State/RemoteExecutionContext.cs delete mode 100644 ByondLang/WorkerStartup.cs diff --git a/ByondInterpretedLanguage.sln b/ByondInterpretedLanguage.sln index af4cbf5..eff6815 100644 --- a/ByondInterpretedLanguage.sln +++ b/ByondInterpretedLanguage.sln @@ -5,8 +5,6 @@ VisualStudioVersion = 16.0.30011.22 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ByondLang", "ByondLang\ByondLang.csproj", "{06AAC521-E559-48EA-B58C-BF69C37D0416}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ByondLang.Api", "ByondLang.Api\ByondLang.Api.csproj", "{C92D3FBA-C22A-42CC-BFED-3D0CE0539733}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -17,10 +15,6 @@ Global {06AAC521-E559-48EA-B58C-BF69C37D0416}.Debug|Any CPU.Build.0 = Debug|Any CPU {06AAC521-E559-48EA-B58C-BF69C37D0416}.Release|Any CPU.ActiveCfg = Release|Any CPU {06AAC521-E559-48EA-B58C-BF69C37D0416}.Release|Any CPU.Build.0 = Release|Any CPU - {C92D3FBA-C22A-42CC-BFED-3D0CE0539733}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C92D3FBA-C22A-42CC-BFED-3D0CE0539733}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C92D3FBA-C22A-42CC-BFED-3D0CE0539733}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C92D3FBA-C22A-42CC-BFED-3D0CE0539733}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/ByondLang/ByondLang.csproj b/ByondLang/ByondLang.csproj index e19eb1e..7ddfbdb 100644 --- a/ByondLang/ByondLang.csproj +++ b/ByondLang/ByondLang.csproj @@ -10,7 +10,6 @@ - @@ -22,8 +21,4 @@ - - - - diff --git a/ByondLang/ChakraCore/Hosting/JsDiagBreakOnExceptionAttributes.cs b/ByondLang/ChakraCore/Hosting/JsDiagBreakOnExceptionAttributes.cs new file mode 100644 index 0000000..683ad6d --- /dev/null +++ b/ByondLang/ChakraCore/Hosting/JsDiagBreakOnExceptionAttributes.cs @@ -0,0 +1,21 @@ +namespace ByondLang.ChakraCore.Hosting +{ + /// + /// Break on Exception attributes. + /// + public enum JsDiagBreakOnExceptionAttributes + { + /// + /// Don't break on any exception. + /// + None = 0x0, + /// + /// Break on uncaught exception. + /// + Uncaught = 0x1, + /// + /// Break on first chance exception. + /// + FirstChance = 0x2 + }; +} \ No newline at end of file diff --git a/ByondLang/ChakraCore/Hosting/JsDiagDebugEvent.cs b/ByondLang/ChakraCore/Hosting/JsDiagDebugEvent.cs index 9e3259a..291a907 100644 --- a/ByondLang/ChakraCore/Hosting/JsDiagDebugEvent.cs +++ b/ByondLang/ChakraCore/Hosting/JsDiagDebugEvent.cs @@ -1,7 +1,7 @@ namespace ByondLang.ChakraCore.Hosting { /// - /// The type of a typed JavaScript array. + /// Debug events reported from ChakraCore engine. /// public enum JsDiagDebugEvent { diff --git a/ByondLang/ChakraCore/Hosting/JsDiagStepType.cs b/ByondLang/ChakraCore/Hosting/JsDiagStepType.cs new file mode 100644 index 0000000..a29493a --- /dev/null +++ b/ByondLang/ChakraCore/Hosting/JsDiagStepType.cs @@ -0,0 +1,33 @@ +namespace ByondLang.ChakraCore.Hosting +{ + /// + /// Stepping types. + /// + public enum JsDiagStepType + { + /// + /// Perform a step operation to next statement. + /// + StepIn = 0, + /// + /// Perform a step out from the current function. + /// + StepOut, + /// + /// Perform a single step over after a debug break if the next statement is a function call, else behaves as a stepin. + /// + StepOver, + /// + /// Perform a single step back to the previous statement (only applicable in TTD mode). + /// + StepBack, + /// + /// Perform a reverse continue operation (only applicable in TTD mode). + /// + ReverseContinue, + /// + /// Perform a forward continue operation. Clears any existing step value. + /// + Continue + }; +} \ No newline at end of file diff --git a/ByondLang/ChakraCore/Hosting/JsRuntime.cs b/ByondLang/ChakraCore/Hosting/JsRuntime.cs index df3604c..df347f8 100644 --- a/ByondLang/ChakraCore/Hosting/JsRuntime.cs +++ b/ByondLang/ChakraCore/Hosting/JsRuntime.cs @@ -215,5 +215,35 @@ public JsContext CreateContext() Native.ThrowIfError(Native.JsCreateContext(this, out JsContext reference)); return reference; } + + /// + /// Starts debugging in the given runtime. + /// + /// Registers a callback to be called on every JsDiagDebugEvent. + /// User provided state that will be passed back to the callback. + /// + /// The runtime should be active on the current thread and should not be in debug state. + /// + public void StartDebugging(JsDiagDebugEventCallback debugEventCallback, IntPtr callbackState) + { + Native.ThrowIfError(Native.JsDiagStartDebugging(this, debugEventCallback, callbackState)); + } + + /// + /// Stops debugging in the given runtime. + /// + /// Runtime to stop debugging. + /// User provided state that was passed in JsDiagStartDebugging. + /// + /// The code JsNoError if the operation succeeded, a failure code otherwise. + /// + /// + /// The runtime should be active on the current thread and in debug state. + /// + public IntPtr StopDebugging() + { + Native.ThrowIfError(Native.JsDiagStopDebugging(this, out IntPtr callbackState)); + return callbackState; + } } } \ No newline at end of file diff --git a/ByondLang/ChakraCore/Hosting/JsValue.cs b/ByondLang/ChakraCore/Hosting/JsValue.cs index 0d276ae..1d2ebe1 100644 --- a/ByondLang/ChakraCore/Hosting/JsValue.cs +++ b/ByondLang/ChakraCore/Hosting/JsValue.cs @@ -920,7 +920,7 @@ public JsValue CallFunction(params JsValue[] arguments) if (arguments.Length > ushort.MaxValue) { - throw new ArgumentOutOfRangeException("arguments"); + throw new ArgumentOutOfRangeException(nameof(arguments)); } Native.ThrowIfError(Native.JsCallFunction(this, arguments, (ushort)arguments.Length, out JsValue returnReference)); @@ -939,7 +939,7 @@ public JsValue ConstructObject(params JsValue[] arguments) { if (arguments.Length > ushort.MaxValue) { - throw new ArgumentOutOfRangeException("arguments"); + throw new ArgumentOutOfRangeException(nameof(arguments)); } Native.ThrowIfError(Native.JsConstructObject(this, arguments, (ushort)arguments.Length, out JsValue returnReference)); return returnReference; diff --git a/ByondLang/ChakraCore/Hosting/Native.cs b/ByondLang/ChakraCore/Hosting/Native.cs index 0597069..742e241 100644 --- a/ByondLang/ChakraCore/Hosting/Native.cs +++ b/ByondLang/ChakraCore/Hosting/Native.cs @@ -528,6 +528,249 @@ internal static extern JsErrorCode JsSetHostPromiseRejectionTracker( [DllImport(DllName)] internal static extern JsErrorCode JsDiagGetBreakpoints(uint scriptId, uint lineNumber, uint columnNumber, out JsValue breakpoint); + /// + /// Remove a breakpoint. + /// + /// Breakpoint id returned from JsDiagSetBreakpoint. + /// + /// The code JsNoError if the operation succeeded, a failure code otherwise. + /// + /// + /// The current runtime should be in debug state. This API can be called when runtime is at a break or running. + /// + [DllImport(DllName)] + internal static extern JsErrorCode JsDiagRemoveBreakpoint(uint scriptIdbreakpointId); + + /// + /// Sets break on exception handling. + /// + /// Runtime to set break on exception attributes. + /// Mask of JsDiagBreakOnExceptionAttributes to set. + /// + /// + /// If this API is not called the default value is set to JsDiagBreakOnExceptionAttributeUncaught in the runtime. + /// + /// + /// + /// The code JsNoError if the operation succeeded, a failure code otherwise. + /// + /// + /// The runtime should be in debug state. This API can be called from another runtime. + /// + [DllImport(DllName)] + internal static extern JsErrorCode JsDiagSetBreakOnException(JsRuntime runtime, JsDiagBreakOnExceptionAttributes exceptionAttributes); + + /// + /// Gets break on exception setting. + /// + /// Runtime from which to get break on exception attributes, should be in debug mode. + /// Mask of JsDiagBreakOnExceptionAttributes. + /// + /// The code JsNoError if the operation succeeded, a failure code otherwise. + /// + /// + /// The runtime should be in debug state. This API can be called from another runtime. + /// + [DllImport(DllName)] + internal static extern JsErrorCode JsDiagGetBreakOnException(JsRuntime runtime, out JsDiagBreakOnExceptionAttributes exceptionAttributes); + + /// + /// Sets the step type in the runtime after a debug break. + /// + /// + /// Requires to be at a debug break. + /// + /// Type of JsDiagStepType. + /// + /// The code JsNoError if the operation succeeded, a failure code otherwise. + /// + /// + /// The current runtime should be in debug state. This API can only be called when runtime is at a break. + /// + [DllImport(DllName)] + internal static extern JsErrorCode JsDiagSetStepType(JsDiagStepType stepType); + + /// + /// Gets list of scripts. + /// + /// Array of script objects. + /// + /// + /// [{ + /// "scriptId" : 2, + /// "fileName" : "c:\\Test\\Test.js", + /// "lineCount" : 4, + /// "sourceLength" : 111 + /// }, { + /// "scriptId" : 3, + /// "parentScriptId" : 2, + /// "scriptType" : "eval code", + /// "lineCount" : 1, + /// "sourceLength" : 12 + /// }] + /// + /// + /// + /// The code JsNoError if the operation succeeded, a failure code otherwise. + /// + /// + /// The current runtime should be in debug state. This API can be called when runtime is at a break or running. + /// + [DllImport(DllName)] + internal static extern JsErrorCode JsDiagGetScripts(out JsValue scriptsArray); + + /// + /// Gets source for a specific script identified by scriptId from JsDiagGetScripts. + /// + /// Id of the script. + /// Source object. + /// + /// + /// { + /// "scriptId" : 1, + /// "fileName" : "c:\\Test\\Test.js", + /// "lineCount" : 12, + /// "sourceLength" : 15154, + /// "source" : "var x = 1;" + /// } + /// + /// + /// + /// The code JsNoError if the operation succeeded, a failure code otherwise. + /// + /// + /// The current runtime should be in debug state. This API can be called when runtime is at a break or running. + /// + [DllImport(DllName)] + internal static extern JsErrorCode JsDiagGetSource(uint scriptId, out JsValue source); + + /// + /// Gets the source information for a function object. + /// + /// JavaScript function. + /// Function position - scriptId, start line, start column, line number of first statement, column number of first statement. + /// + /// + /// { + /// "scriptId" : 1, + /// "fileName" : "c:\\Test\\Test.js", + /// "line" : 1, + /// "column" : 2, + /// "firstStatementLine" : 6, + /// "firstStatementColumn" : 0 + /// } + /// + /// + /// + /// The code JsNoError if the operation succeeded, a failure code otherwise. + /// + /// + /// This API can be called when runtime is at a break or running. + /// + [DllImport(DllName)] + internal static extern JsErrorCode JsDiagGetFunctionPosition(JsValue function, out JsValue functionPosition); + + /// + /// Gets the stack trace information. + /// + /// Stack trace information. + /// + /// + /// [{ + /// "index" : 0, + /// "scriptId" : 2, + /// "line" : 3, + /// "column" : 0, + /// "sourceLength" : 9, + /// "sourceText" : "var x = 1", + /// "functionHandle" : 1 + /// }] + /// + /// + /// + /// The code JsNoError if the operation succeeded, a failure code otherwise. + /// + /// + /// The current runtime should be in debug state. This API can only be called when runtime is at a break. + /// + [DllImport(DllName)] + internal static extern JsErrorCode JsDiagGetStackTrace(out JsValue stackTrace); + + /// + /// Gets the list of properties corresponding to the frame. + /// + /// Index of stack frame from JsDiagGetStackTrace. + /// Object of properties array (properties, scopes and globals). + /// + /// + /// propertyAttributes is a bit mask of + /// NONE = 0x1, + /// HAVE_CHILDRENS = 0x2, + /// READ_ONLY_VALUE = 0x4, + /// IN_TDZ = 0x8, + /// + /// + /// { + /// "thisObject": { + /// "name": "this", + /// "type" : "object", + /// "className" : "Object", + /// "display" : "{...}", + /// "propertyAttributes" : 1, + /// "handle" : 306 + /// }, + /// "exception" : { + /// "name" : "{exception}", + /// "type" : "object", + /// "display" : "'a' is undefined", + /// "className" : "Error", + /// "propertyAttributes" : 1, + /// "handle" : 307 + /// } + /// "arguments" : { + /// "name" : "arguments", + /// "type" : "object", + /// "display" : "{...}", + /// "className" : "Object", + /// "propertyAttributes" : 1, + /// "handle" : 190 + /// }, + /// "returnValue" : { + /// "name" : "[Return value]", + /// "type" : "undefined", + /// "propertyAttributes" : 0, + /// "handle" : 192 + /// }, + /// "functionCallsReturn" : [{ + /// "name" : "[foo1 returned]", + /// "type" : "number", + /// "value" : 1, + /// "propertyAttributes" : 2, + /// "handle" : 191 + /// } + /// ], + /// "locals" : [], + /// "scopes" : [{ + /// "index" : 0, + /// "handle" : 193 + /// } + /// ], + /// "globals" : { + /// "handle" : 194 + /// } + /// } + /// + /// + /// + /// The code JsNoError if the operation succeeded, a failure code otherwise. + /// + /// + /// The current runtime should be in debug state. This API can only be called when runtime is at a break. + /// + [DllImport(DllName)] + internal static extern JsErrorCode JsDiagGetStackProperties(uint stackFrameIndex, out JsValue properties); + + // TODO: // https://github.com/Microsoft/ChakraCore/issues/4324 diff --git a/ByondLang/ChakraCore/JsPFIFOScheduler.cs b/ByondLang/ChakraCore/JsPFIFOScheduler.cs index c1d13c8..31b570f 100644 --- a/ByondLang/ChakraCore/JsPFIFOScheduler.cs +++ b/ByondLang/ChakraCore/JsPFIFOScheduler.cs @@ -1,85 +1,131 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace ByondLang.ChakraCore -{ - public class JsPFIFOScheduler : JsScheduler, IDisposable - { - private object aliveLock = new object(); - private bool alive = true; - private Thread thread; - private LinkedList tasks = new LinkedList(); - private AutoResetEvent newTaskEvent = new AutoResetEvent(false); - - - public JsPFIFOScheduler() - { - thread = new Thread(ExecutionThread); - thread.Start(); - } - - private JsTask FirstToExecute() - { - LinkedListNode lowest = tasks.First; - for(var node = tasks.First; node != null; node = node.Next) - { - if (lowest != null && lowest.Value.Priority > node.Value.Priority) - lowest = node; - - } - var value = lowest?.Value; - if(lowest != null) - tasks.Remove(lowest); - return value; - } - - public override void QueueTask(JsTask task) - { - newTaskEvent.Reset(); - lock (tasks) - _ = tasks.AddLast(task); - newTaskEvent.Set(); - } - - private void ExecutionThread() - { - bool shouldStayAlive = true; - JsTask currentlyExecuting = null; - while (shouldStayAlive) - { - if (currentlyExecuting == null) - { - lock (tasks) - { - currentlyExecuting = FirstToExecute(); - } - } - - if (currentlyExecuting != null) - { - currentlyExecuting.Run(); - currentlyExecuting = null; - } - else - { - lock (aliveLock) - shouldStayAlive = alive; - if (shouldStayAlive) - newTaskEvent.WaitOne(); - } - } - } - - public void Dispose() - { - lock (aliveLock) - alive = false; - newTaskEvent.Reset(); - Thread.SpinWait(1); - newTaskEvent.Set(); - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace ByondLang.ChakraCore +{ + public class JsPFIFOScheduler : JsScheduler, IDisposable + { + private readonly object aliveLock = new object(); + private bool alive = true; + private Thread thread; + private LinkedList tasks = new LinkedList(); + private LinkedList debugTasks = new LinkedList(); + private AutoResetEvent newTaskEvent = new AutoResetEvent(false); + private AutoResetEvent newDebugTaskEvent = new AutoResetEvent(false); + + private JsTask currentlyExecuting = null; + private object inBreakLock = new object(); + private bool inBreak = false; + + public JsPFIFOScheduler() + { + thread = new Thread(ExecutionThread); + thread.Start(); + } + + private JsTask FirstToExecute(LinkedList tasks) + { + LinkedListNode lowest = tasks.First; + for(var node = tasks.First; node != null; node = node.Next) + { + if (lowest != null && lowest.Value.Priority > node.Value.Priority) + lowest = node; + + } + var value = lowest?.Value; + if(lowest != null) + tasks.Remove(lowest); + return value; + } + + public override void QueueTask(JsTask task) + { + newTaskEvent.Reset(); + lock (tasks) + _ = tasks.AddLast(task); + newTaskEvent.Set(); + } + + private void ExecutionThread() + { + bool shouldStayAlive = true; + while (shouldStayAlive) + { + lock (tasks) + currentlyExecuting = FirstToExecute(tasks); + + if (currentlyExecuting != null) + { + currentlyExecuting.Run(); + currentlyExecuting = null; + } + else + { + newTaskEvent.WaitOne(); + } + lock (aliveLock) + shouldStayAlive = alive; + } + } + + public override void QueueTaskDebug(JsTask task) + { + newDebugTaskEvent.Reset(); + lock (debugTasks) + _ = debugTasks.AddLast(task); + newDebugTaskEvent.Set(); + } + + public override void EnterBreakState() + { + if (Thread.CurrentThread != thread) + throw new InvalidOperationException("Current thread is not same as scheduler's thread."); + bool breakState = true; + lock (inBreakLock) + inBreak = true; + + currentlyExecuting.OnBreak(); + JsTask currentlyExecutingDebug = null; + while (breakState) + { + lock (debugTasks) + currentlyExecutingDebug = FirstToExecute(debugTasks); + + if (currentlyExecutingDebug != null) + { + currentlyExecutingDebug.Run(); + currentlyExecutingDebug = null; + } else + { + newDebugTaskEvent.WaitOne(); + } + + lock (inBreakLock) + breakState = inBreak; + } + currentlyExecuting.OnResume(); + } + + public override void ExitBreakState() + { + newDebugTaskEvent.Reset(); + lock (inBreakLock) + inBreak = false; + newDebugTaskEvent.Set(); + } + + public void Dispose() + { + lock (aliveLock) + alive = false; + newTaskEvent.Reset(); + newDebugTaskEvent.Reset(); + Thread.SpinWait(1); + newTaskEvent.Set(); + newDebugTaskEvent.Set(); + } + } +} diff --git a/ByondLang/ChakraCore/JsScheduler.cs b/ByondLang/ChakraCore/JsScheduler.cs index 73ed3d1..687e425 100644 --- a/ByondLang/ChakraCore/JsScheduler.cs +++ b/ByondLang/ChakraCore/JsScheduler.cs @@ -1,29 +1,43 @@ using ByondLang.Interface; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace ByondLang.ChakraCore -{ - public abstract class JsScheduler - { - protected BaseProgram program; - public JsScheduler(BaseProgram program) - { - this.program = program; - } - - public abstract void EnterBreakState(); - public abstract void QueueTask(JsTask task); - public abstract void QueueTaskDebug(JsTask task); - - public virtual JsTask Run(Func func, JsTaskPriority priority = JsTaskPriority.LOWEST) => - new JsTask(func, priority).Start(this); - public virtual JsTask Run(Action action, JsTaskPriority priority = JsTaskPriority.LOWEST) => new JsTask(action, priority).Start(this); - +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace ByondLang.ChakraCore +{ + public abstract class JsScheduler + { + + /// + /// Enters blocking call and processes tasks scheduled via QueueTaskDebug. + /// + public abstract void EnterBreakState(); + public abstract void ExitBreakState(); + public abstract void QueueTask(JsTask task); + public abstract void QueueTaskDebug(JsTask task); + + public virtual JsTask Run(Func func, JsTaskPriority priority = JsTaskPriority.LOWEST) => + new JsTask(func, priority).Start(this); + public virtual JsTask Run(Action action, JsTaskPriority priority = JsTaskPriority.LOWEST) => + new JsTask(action, priority).Start(this); + + + public virtual JsTask RunTimed(Func func, Action onTimeout, TimeSpan timeout, JsTaskPriority priority = JsTaskPriority.LOWEST) => + new JsTaskTimed(func, onTimeout, timeout, priority).Start(this); + public virtual JsTask RunTimed(Action action, Action onTimeout, TimeSpan timeout, JsTaskPriority priority = JsTaskPriority.LOWEST) => + new JsTaskTimed(action, onTimeout, timeout, priority).Start(this); + + + public virtual JsTask RunDebug(Func func, JsTaskPriority priority = JsTaskPriority.LOWEST) => - new JsTask(func, priority).StartDebug(this); - public virtual JsTask RunDebug(Action action, JsTaskPriority priority = JsTaskPriority.LOWEST) => new JsTask(action, priority).StartDebug(this); - } -} + new JsTask(func, priority).StartDebug(this); + public virtual JsTask RunDebug(Action action, JsTaskPriority priority = JsTaskPriority.LOWEST) => + new JsTask(action, priority).StartDebug(this); + + public virtual JsTask RunDebugTimed(Func func, Action onTimeout, TimeSpan timeout, JsTaskPriority priority = JsTaskPriority.LOWEST) => + new JsTaskTimed(func, onTimeout, timeout, priority).StartDebug(this); + public virtual JsTask RunDebugTimed(Action action, Action onTimeout, TimeSpan timeout, JsTaskPriority priority = JsTaskPriority.LOWEST) => + new JsTaskTimed(action, onTimeout, timeout, priority).StartDebug(this); + } +} diff --git a/ByondLang/ChakraCore/JsTask.cs b/ByondLang/ChakraCore/JsTask.cs index 02d5364..d8d0338 100644 --- a/ByondLang/ChakraCore/JsTask.cs +++ b/ByondLang/ChakraCore/JsTask.cs @@ -1,206 +1,208 @@ using ByondLang.Interface; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Threading; -using System.Threading.Tasks; - -namespace ByondLang.ChakraCore -{ - public enum JsTaskPriority - { - INITIALIZATION, - EXECUTION, - CALLBACK, - PROMISE, - LOWEST, - } - - public enum JsTaskState - { - Initialized, - Pending, - Running, - Complete, - Failed - } - - public class JsTask - { - public bool IsCompleted => State == JsTaskState.Failed || State == JsTaskState.Complete; - public JsTaskPriority Priority { get; protected set; } - public JsTaskState State { get; protected set; } = JsTaskState.Initialized; - - private Action m_action; - protected LinkedList finishedCallbacks = new LinkedList(); - - protected JsTask() { } - - public JsTask(Action function, JsTaskPriority priority = JsTaskPriority.LOWEST) - { - m_action = function; - Priority = priority; - } - - public virtual void Run() - { - if (IsCompleted) - { - throw new Exception("Task is already complete."); - } - State = JsTaskState.Running; - try - { - m_action(); - } - catch (Exception) - { - State = JsTaskState.Failed; - throw; - } - finally - { - State = JsTaskState.Complete; - } - foreach (Action callback in finishedCallbacks) - { - callback(); - } - finishedCallbacks.Clear(); - } - - public JsTask Start(JsScheduler scheduler) - { - scheduler.QueueTask(this); - return this; - } - - public JsTask StartDebug(JsScheduler scheduler) - { - scheduler.QueueTaskDebug(this); +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; + +namespace ByondLang.ChakraCore +{ + public enum JsTaskPriority + { + INITIALIZATION, + EXECUTION, + CALLBACK, + PROMISE, + LOWEST, + } + + public enum JsTaskState + { + Initialized, + Pending, + Running, + Complete, + Failed + } + + public class JsTask + { + public bool IsCompleted => State == JsTaskState.Failed || State == JsTaskState.Complete; + public JsTaskPriority Priority { get; protected set; } + public JsTaskState State { get; protected set; } = JsTaskState.Initialized; + + private Action m_action; + protected LinkedList finishedCallbacks = new LinkedList(); + + protected JsTask() { } + + public JsTask(Action function, JsTaskPriority priority = JsTaskPriority.LOWEST) + { + m_action = function; + Priority = priority; + } + + public virtual void Run() + { + if (IsCompleted) + { + throw new Exception("Task is already complete."); + } + State = JsTaskState.Running; + try + { + m_action(); + } + catch (Exception) + { + State = JsTaskState.Failed; + throw; + } + finally + { + State = JsTaskState.Complete; + } + foreach (Action callback in finishedCallbacks) + { + callback(); + } + finishedCallbacks.Clear(); + } + + public JsTask Start(JsScheduler scheduler) + { + scheduler.QueueTask(this); + return this; + } + + public JsTask StartDebug(JsScheduler scheduler) + { + scheduler.QueueTaskDebug(this); return this; - } - - public JsTaskAwaiter GetAwaiter() - { - return new JsTaskAwaiter(this); - } - - public void RegisterOnCompleteCallback(Action action) - { - finishedCallbacks.AddLast(action); - } - - - public bool Wait(TimeSpan timeout) => SpinWait.SpinUntil(() => IsCompleted, timeout); - public void Wait() => SpinWait.SpinUntil(() => IsCompleted); - - public bool Wait(int timeout) => Wait(new TimeSpan(TimeSpan.TicksPerMillisecond * timeout)); - } - - public class JsTask: JsTask - { - Delegate m_action; - TResult result = default; - - public JsTask(Func function, JsTaskPriority priority = JsTaskPriority.LOWEST, BaseProgram program = null) - { - m_action = function; - Priority = priority; - } - - public override void Run() - { - if (IsCompleted) - { - throw new Exception("Task is already complete."); - } - State = JsTaskState.Running; - try - { - if (m_action is Action action) - { - action(); - } - else if (m_action is Func func) - { - result = func(); - } - else - { - throw new ArgumentException("Action type is not supported."); - } - } - catch (Exception) - { - State = JsTaskState.Failed; - throw; - } - finally - { - State = JsTaskState.Complete; - } - foreach (Action callback in finishedCallbacks) - { - callback(); - } - finishedCallbacks.Clear(); - } - - public new JsTask Start(JsScheduler scheduler) - { - scheduler.QueueTask(this); - return this; - } - - public new JsTask StartDebug(JsScheduler scheduler) - { - scheduler.QueueTaskDebug(this); - return this; - } - - public new JsTaskAwaiter GetAwaiter() - { - return new JsTaskAwaiter(this); - } - - public TResult GetResult() - { - return result; - } - - - } - - public class JsTaskAwaiter : INotifyCompletion - { - protected JsTask _task; - - public bool IsCompleted => _task.IsCompleted; - - protected JsTaskAwaiter() { } - public JsTaskAwaiter(JsTask task) - { - _task = task; - } - - public void OnCompleted(Action continuation) => _task.RegisterOnCompleteCallback(continuation); - - public object GetResult() => null; - } - - public class JsTaskAwaiter : JsTaskAwaiter - { - protected JsTask _ttask; - - - public JsTaskAwaiter(JsTask task) - { - _task = task; - _ttask = task; - } - - public new TResult GetResult() => _ttask.GetResult(); - } -} + } + + public JsTaskAwaiter GetAwaiter() + { + return new JsTaskAwaiter(this); + } + + public void RegisterOnCompleteCallback(Action action) + { + finishedCallbacks.AddLast(action); + } + + public virtual void OnBreak() { } + public virtual void OnResume() { } + + public bool Wait(TimeSpan timeout) => SpinWait.SpinUntil(() => IsCompleted, timeout); + public void Wait() => SpinWait.SpinUntil(() => IsCompleted); + + public bool Wait(int timeout) => Wait(new TimeSpan(TimeSpan.TicksPerMillisecond * timeout)); + } + + public class JsTask: JsTask + { + Delegate m_action; + TResult result = default; + + public JsTask(Func function, JsTaskPriority priority = JsTaskPriority.LOWEST) + { + m_action = function; + Priority = priority; + } + + public override void Run() + { + if (IsCompleted) + { + throw new Exception("Task is already complete."); + } + State = JsTaskState.Running; + try + { + if (m_action is Action action) + { + action(); + } + else if (m_action is Func func) + { + result = func(); + } + else + { + throw new ArgumentException("Action type is not supported."); + } + } + catch (Exception) + { + State = JsTaskState.Failed; + throw; + } + finally + { + State = JsTaskState.Complete; + } + foreach (Action callback in finishedCallbacks) + { + callback(); + } + finishedCallbacks.Clear(); + } + + public new JsTask Start(JsScheduler scheduler) + { + scheduler.QueueTask(this); + return this; + } + + public new JsTask StartDebug(JsScheduler scheduler) + { + scheduler.QueueTaskDebug(this); + return this; + } + + public new JsTaskAwaiter GetAwaiter() + { + return new JsTaskAwaiter(this); + } + + public TResult GetResult() + { + return result; + } + + + } + + public class JsTaskAwaiter : INotifyCompletion + { + protected JsTask _task; + + public bool IsCompleted => _task.IsCompleted; + + protected JsTaskAwaiter() { } + public JsTaskAwaiter(JsTask task) + { + _task = task; + } + + public void OnCompleted(Action continuation) => _task.RegisterOnCompleteCallback(continuation); + + public object GetResult() => null; + } + + public class JsTaskAwaiter : JsTaskAwaiter + { + protected JsTask _ttask; + + + public JsTaskAwaiter(JsTask task) + { + _task = task; + _ttask = task; + } + + public new TResult GetResult() => _ttask.GetResult(); + } +} diff --git a/ByondLang/ChakraCore/JsTaskTimed.cs b/ByondLang/ChakraCore/JsTaskTimed.cs new file mode 100644 index 0000000..0e9f518 --- /dev/null +++ b/ByondLang/ChakraCore/JsTaskTimed.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace ByondLang.ChakraCore +{ + public class JsTaskTimed : JsTask + { + protected Action timeoutAction; + protected Stopwatch timer = new Stopwatch(); + protected TimeSpan timeout; + + + public JsTaskTimed(Action function, Action onTimeout, TimeSpan timeout, JsTaskPriority priority = JsTaskPriority.LOWEST) : base(function, priority) + { + timeoutAction = onTimeout; + this.timeout = timeout; + } + + public override void Run() + { + timer.Start(); + using (var timer = new Timer(TimmerCheck, null, TimeSpan.Zero, new TimeSpan(10_000))) + { + base.Run(); + } + timer.Stop(); + } + + private void TimmerCheck(object state) + { + if(timer.Elapsed > timeout) + { + timeoutAction(); + timer.Stop(); + } + } + + public override void OnBreak() + { + base.OnBreak(); + timer.Stop(); + } + + public override void OnResume() + { + base.OnResume(); + timer.Start(); + } + } + + public class JsTaskTimed : JsTask + { + protected Action timeoutAction; + protected Stopwatch timer = new Stopwatch(); + protected TimeSpan timeout; + + + public JsTaskTimed(Func function, Action onTimeout, TimeSpan timeout, JsTaskPriority priority = JsTaskPriority.LOWEST) : base(function, priority) + { + timeoutAction = onTimeout; + this.timeout = timeout; + } + + public override void Run() + { + timer.Start(); + using (var timer = new Timer(TimmerCheck, null, TimeSpan.Zero, new TimeSpan(10_000))) + { + base.Run(); + } + timer.Stop(); + } + + private void TimmerCheck(object state) + { + if (timer.Elapsed > timeout) + { + timeoutAction(); + timer.Stop(); + } + } + + public override void OnBreak() + { + base.OnBreak(); + timer.Stop(); + } + + public override void OnResume() + { + base.OnResume(); + timer.Start(); + } + } +} diff --git a/ByondLang/ChakraCore/TypeMapper.cs b/ByondLang/ChakraCore/TypeMapper.cs index 66e1f40..ef70e03 100644 --- a/ByondLang/ChakraCore/TypeMapper.cs +++ b/ByondLang/ChakraCore/TypeMapper.cs @@ -15,7 +15,8 @@ namespace ByondLang.ChakraCore { - public class TypeMapper : IDisposable + [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0039:Use local function", Justification = "GC issues")] + public class TypeMapper : IDisposable { /// /// Name of property to store the external object @@ -192,16 +193,14 @@ public object MapToHostType(JsValue value) private EmbeddedObject CreateEmbeddedObjectOrFunction(object obj) { - var del = obj as Delegate; - var delArray = obj as Delegate[]; - if (delArray != null) - return CreateEmbeddedFunction(delArray); - else if (del != null) - return CreateEmbeddedFunction(del); - else - return CreateEmbeddedObject(obj); + if (obj is Delegate[] delArray) + return CreateEmbeddedFunction(delArray); + else if (obj is Delegate del) + return CreateEmbeddedFunction(del); + else + return CreateEmbeddedObject(obj); - } + } private EmbeddedObject CreateEmbeddedObject(object obj) { @@ -221,7 +220,8 @@ private EmbeddedObject CreateEmbeddedObject(object obj) return embeddedObject; } - private void ProjectFields(EmbeddedItem externalItem, EmbeddingObjectOptions options) + + private void ProjectFields(EmbeddedItem externalItem, EmbeddingObjectOptions options) { Type type = externalItem.HostType; object obj = externalItem.HostObject; @@ -256,10 +256,9 @@ private void ProjectFields(EmbeddedItem externalItem, EmbeddingObjectOptions opt catch (Exception e) { Exception exception = UnwrapException(e); - var wrapperException = exception as JsException; - JsValue errorValue; + JsValue errorValue; - if (wrapperException != null) + if (exception is JsException wrapperException) { errorValue = CreateErrorFromWrapperException(wrapperException); } @@ -304,10 +303,9 @@ private void ProjectFields(EmbeddedItem externalItem, EmbeddingObjectOptions opt catch (Exception e) { Exception exception = UnwrapException(e); - var wrapperException = exception as JsException; - JsValue errorValue; + JsValue errorValue; - if (wrapperException != null) + if (exception is JsException wrapperException) { errorValue = CreateErrorFromWrapperException(wrapperException); } @@ -374,10 +372,9 @@ private void ProjectProperties(EmbeddedItem externalItem, EmbeddingObjectOptions catch (Exception e) { Exception exception = UnwrapException(e); - var wrapperException = exception as JsException; - JsValue errorValue; + JsValue errorValue; - if (wrapperException != null) + if (exception is JsException wrapperException) { errorValue = CreateErrorFromWrapperException(wrapperException); } @@ -427,10 +424,9 @@ private void ProjectProperties(EmbeddedItem externalItem, EmbeddingObjectOptions catch (Exception e) { Exception exception = UnwrapException(e); - var wrapperException = exception as JsException; - JsValue errorValue; + JsValue errorValue; - if (wrapperException != null) + if (exception is JsException wrapperException) { errorValue = CreateErrorFromWrapperException(wrapperException); } @@ -502,10 +498,9 @@ private void ProjectMethods(EmbeddedItem externalItem, EmbeddingObjectOptions op catch (Exception e) { Exception exception = UnwrapException(e); - var wrapperException = exception as JsException; - JsValue errorValue; + JsValue errorValue; - if (wrapperException != null) + if (exception is JsException wrapperException) { errorValue = CreateErrorFromWrapperException(wrapperException); } @@ -565,12 +560,11 @@ private EmbeddedObject CreateEmbeddedFunction(Delegate del) { JsValue undefinedValue = JsValue.Undefined; Exception exception = UnwrapException(e); - var wrapperException = exception as JsException; - JsValue errorValue = wrapperException != null ? - CreateErrorFromWrapperException(wrapperException) - : - JsValue.CreateError(JsValue.FromString($"Host delegate invocation error: {exception.Message}")); - ; + JsValue errorValue = exception is JsException wrapperException ? + CreateErrorFromWrapperException(wrapperException) + : + JsValue.CreateError(JsValue.FromString($"Host delegate invocation error: {exception.Message}")); + ; JsContext.SetException(errorValue); return undefinedValue; @@ -613,12 +607,11 @@ private EmbeddedObject CreateEmbeddedFunction(Delegate[] del) { JsValue undefinedValue = JsValue.Undefined; Exception exception = UnwrapException(e); - var wrapperException = exception as JsException; - JsValue errorValue = wrapperException != null ? - CreateErrorFromWrapperException(wrapperException) - : - JsValue.CreateError(JsValue.FromString($"Host delegate invocation error: {exception.Message}")); - ; + JsValue errorValue = exception is JsException wrapperException ? + CreateErrorFromWrapperException(wrapperException) + : + JsValue.CreateError(JsValue.FromString($"Host delegate invocation error: {exception.Message}")); + ; JsContext.SetException(errorValue); return undefinedValue; @@ -828,18 +821,17 @@ public static bool AreTypesCompatible(JsValueType jsType, Type type) private static Exception UnwrapException(Exception exception) { Exception originalException = exception; - var targetInvocationException = exception as TargetInvocationException; - if (targetInvocationException != null) - { - Exception innerException = targetInvocationException.InnerException; - if (innerException != null) - { - originalException = innerException; - } - } + if (exception is TargetInvocationException targetInvocationException) + { + Exception innerException = targetInvocationException.InnerException; + if (innerException != null) + { + originalException = innerException; + } + } - return originalException; + return originalException; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -863,10 +855,9 @@ private static void CreateAndSetError(string message) private static JsValue CreateErrorFromWrapperException(JsException exception) { - var originalException = exception.InnerException as JsException; - JsErrorCode errorCode = originalException != null ? - originalException.ErrorCode : JsErrorCode.NoError; - var description = Enum.GetName(typeof(JsErrorCode), errorCode); + JsErrorCode errorCode = exception.InnerException is JsException originalException ? + originalException.ErrorCode : JsErrorCode.NoError; + var description = Enum.GetName(typeof(JsErrorCode), errorCode); JsValue innerErrorValue = JsValue.CreateError(JsValue.FromString(description)); innerErrorValue.SetProperty("description", JsValue.FromString(description), true); diff --git a/ByondLang/Controllers/NTSLController.cs b/ByondLang/Controllers/NTSLController.cs index 48d3782..98b9a8d 100644 --- a/ByondLang/Controllers/NTSLController.cs +++ b/ByondLang/Controllers/NTSLController.cs @@ -1,7 +1,8 @@ using System; +using System.Collections.Generic; using System.Threading.Tasks; +using ByondLang.Interface; using ByondLang.Models; -using ByondLang.Models.Request; using ByondLang.Services; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; @@ -32,7 +33,15 @@ public int Clear() [HttpGet("/new_program")] public int NewProgram([FromQuery] ProgramType type) { - return _newService.NewProgram(PTypeToPType(type)); + switch (type) + { + case ProgramType.Computer: + return _newService.NewProgram((s) => new ComputerProgram(s)); + case ProgramType.TCom: + throw new NotImplementedException(); + default: + throw new NotImplementedException(); + } } [HttpGet("/execute")] @@ -50,22 +59,41 @@ public int Remove([FromQuery] int id) } - + [HttpGet("/computer/get_buffer")] - public async Task GetBuffer([FromQuery] int id) + public string GetBuffer([FromQuery] int id) { - return await _newService.GetProgram(id).GetBuffer(); + return _newService.GetProgram(id).GetTerminalBuffer(); } [HttpGet("/computer/topic")] - public async Task TopicCall([FromQuery] int id, [FromQuery] string topic = "", [FromQuery] string data = "") + public int TopicCall([FromQuery] int id, [FromQuery] string topic = "", [FromQuery] string data = "") { - var program = _newService.GetProgram(id); - await program.HandleTopic(topic, data); + var program = _newService.GetProgram(id); + program.HandleTopic(topic, data); return 1; } + [HttpGet("/debug/set")] + public int DebugSet([FromQuery] int id, [FromQuery] int state) + { + var program = _newService.GetProgram(id); + program.SetDebuggingState(Convert.ToBoolean(state)); + return 1; + } + /// + /// Gets new events sent by + /// + /// + /// + [HttpGet("/events/get")] + public IEnumerable EventsGet([FromQuery] int id) + { + var program = _newService.GetProgram(id); + program.SetDebuggingState(Convert.ToBoolean(state)); + return 1; + } /* @@ -90,15 +118,5 @@ public enum ProgramType Computer, TCom } - - private Api.ProgramType PTypeToPType(ProgramType type) - { - return type switch - { - ProgramType.Computer => Api.ProgramType.ComputerProgram, - ProgramType.TCom => Api.ProgramType.None, // TODO change this - _ => Api.ProgramType.None, - }; - } } } diff --git a/ByondLang/Interface/BaseProgram.cs b/ByondLang/Interface/BaseProgram.cs index 2b72c82..364251c 100644 --- a/ByondLang/Interface/BaseProgram.cs +++ b/ByondLang/Interface/BaseProgram.cs @@ -1,24 +1,22 @@ using ByondLang.ChakraCore; using ByondLang.ChakraCore.Hosting; +using ByondLang.Models; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using System; +using System.Collections.Concurrent; using System.Collections.Generic; -using System.Linq; using System.Text; -using System.Threading; using System.Threading.Tasks; namespace ByondLang.Interface { - /// - /// Direct replacemnt for original program - /// public class BaseProgram : IDisposable { public const int DEFAULT_SCRIPT_TIMEOUT = 2000; public const int DEFAULT_PROMISE_TIMEOUT = 2000; public const int CALLBACK_HASH_LEN = 12; + public const int EVENT_HASH_LEN = 12; private static Random random = new Random(); @@ -30,14 +28,19 @@ public class BaseProgram : IDisposable protected Task lastExecutionTask; protected Dictionary> callbacks = new Dictionary>(); - private ILogger logger; + protected ILogger logger; + + protected ConcurrentDictionary pendingEvents = new ConcurrentDictionary(); + protected ConcurrentDictionary unresolvedEvents = new ConcurrentDictionary(); + + private bool isDebugging = false; public bool IsInBreak { get; protected set; } public BaseProgram(IServiceProvider serviceProvider) { runtime = JsRuntime.Create(JsRuntimeAttributes.AllowScriptInterrupt); - + logger = serviceProvider?.GetService>(); } @@ -69,10 +72,56 @@ private void PromiseContinuationCallback(JsValue task, IntPtr callbackState) }, priority: JsTaskPriority.PROMISE); } + private void DebugEventCallback(JsDiagDebugEvent debugEvent, JsValue eventData, IntPtr callbackState) + { + IsInBreak = true; + var glob = JsValue.GlobalObject; + var JSON_Stringify = glob.GetProperty("JSON").GetProperty("stringify"); + + var eventDataString = JSON_Stringify.CallFunction(glob, eventData); + + string data = eventDataString.ToString(); + Console.WriteLine(data); + + scheduler.EnterBreakState(); + + IsInBreak = false; + } + + internal IEnumerable GetEvents() + { + var events = new List(); + foreach (var item in pendingEvents) + { + events.Add(item.Value); + if(item.Value.NeedsToBeResolved) + unresolvedEvents[item.Key] = item.Value; + } + return events; + } + + internal void SetDebuggingState(bool enabled) + { + if (!isDebugging && enabled) + { + Function(() => + { + runtime.StartDebugging(DebugEventCallback, IntPtr.Zero); + }, JsTaskPriority.INITIALIZATION); + } + else if (isDebugging && !enabled) + { + Function(() => + { + runtime.StopDebugging(); + }, JsTaskPriority.INITIALIZATION); + } + } + internal JsCallback RegisterCallback(JsValue callback) { var hash = GenerateCallbackHash(); - var call = new JsCallback(hash, callback); + var call = new JsCallback(hash, callback, this); callbacks[hash] = new WeakReference(call); return call; } @@ -91,13 +140,27 @@ private string GenerateCallbackHash() return finalResult; } + private string GenerateEventHash() + { + string characters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + StringBuilder result = new StringBuilder(EVENT_HASH_LEN); + for (int i = 0; i < EVENT_HASH_LEN; i++) + { + result.Append(characters[random.Next(characters.Length)]); + } + var finalResult = result.ToString(); + if (pendingEvents.ContainsKey(finalResult) || unresolvedEvents.ContainsKey(finalResult)) + return GenerateEventHash(); + return finalResult; + } + public virtual void InstallInterfaces() { // Add generic global APIs accessible from everywhere var glob = JsValue.GlobalObject; glob.SetProperty("btoa", typeMapper.MTS((Func)delegate (JsValue value) { - if(value.ValueType != JsValueType.String) + if (value.ValueType != JsValueType.String) { value = value.ConvertToString(); } @@ -132,7 +195,7 @@ public JsTask ExecuteScript(string script) }, HandleException, JsTaskPriority.EXECUTION); } - + #region IDisposable support private bool disposedValue; @@ -140,11 +203,15 @@ protected virtual void Dispose(bool disposing) { if (!disposedValue) { + if (IsInBreak) + scheduler.ExitBreakState(); + if (disposing) { callbacks.Clear(); typeMapper.Dispose(); } + scheduler.Dispose(); context.Release(); runtime.Dispose(); @@ -164,69 +231,40 @@ public void Dispose() } #endregion - #region Timed Function implemetation base - private void TimedFn(int timeout, Action timedAction, Func exHandler) + #region Function wrappers base + private void FnReEx(Action action, Func exHandler) { - using (var timer = new Timer(state => + try { - runtime.Disabled = true; - }, null, timeout, Timeout.Infinite)) + action(); + } + catch (Exception ex) { - try - { - timedAction(); - } - //catch (JsScriptException ex) - //{ - // if (ex.ErrorCode != JsErrorCode.ScriptTerminated) - // if (exHandler != null) - // exHandler(ex); - // else - // throw; - //} - catch (Exception ex) - { - if (exHandler == null || !exHandler(ex)) - throw; - } + if (exHandler == null || !exHandler(ex)) + throw; } runtime.Disabled = false; } - - private R TimedFn(int timeout, Func timedAction, Func exHandler) + private R FnReEx(Func timedAction, Func exHandler) { R result = default; - using (var timer = new Timer(state => + try { - runtime.Disabled = true; - }, null, timeout, Timeout.Infinite)) + result = timedAction(); + } + catch (Exception ex) { - try - { - result = timedAction(); - } - //catch (JsScriptException ex) - //{ - // if (ex.ErrorCode != JsErrorCode.ScriptTerminated) - // if (exHandler != null) - // exHandler(ex); - // else - // throw; - //} - catch (Exception ex) - { - if (exHandler == null || !exHandler(ex)) - throw; - } - + if (exHandler == null || !exHandler(ex)) + throw; } runtime.Disabled = false; return result; } #endregion - #region Timed Function via sheduler implemetations + + #region Function via sheduler implemetations public JsTask TimedFunction(int timeout, Action function, Func exHandler = null, JsTaskPriority priority = JsTaskPriority.LOWEST) => - scheduler.Run(() => TimedFn(timeout, function, exHandler), priority); + scheduler.RunTimed(() => FnReEx(function, exHandler), () => runtime.Disabled = true, TimeSpan.FromMilliseconds(timeout), priority); public JsTask TimedFunction(Action function, Func exHandler = null, JsTaskPriority priority = JsTaskPriority.EXECUTION) => TimedFunction(DEFAULT_SCRIPT_TIMEOUT, function, exHandler, priority); @@ -235,13 +273,15 @@ public JsTask Function(Action function, JsTaskPriority priority = JsTaskPriority scheduler.Run(function, priority); public JsTask TimedFunction(int timeout, Func function, Func exHandler = null, JsTaskPriority priority = JsTaskPriority.LOWEST) => - scheduler.Run(() => TimedFn(timeout, function, exHandler), priority); + scheduler.RunTimed(() => FnReEx(function, exHandler), () => runtime.Disabled = true, TimeSpan.FromMilliseconds(timeout), priority); public JsTask TimedFunction(Func function, Func exHandler = null, JsTaskPriority priority = JsTaskPriority.EXECUTION) => TimedFunction(DEFAULT_SCRIPT_TIMEOUT, function, exHandler, priority); public JsTask Function(Func function, JsTaskPriority priority = JsTaskPriority.EXECUTION) => scheduler.Run(function, priority); + + #endregion } } diff --git a/ByondLang/Interface/ComputerProgram.cs b/ByondLang/Interface/ComputerProgram.cs index f10f206..d0aa86d 100644 --- a/ByondLang/Interface/ComputerProgram.cs +++ b/ByondLang/Interface/ComputerProgram.cs @@ -28,8 +28,7 @@ public void HandleTopic(string hash, string data) if (!callbacks.ContainsKey(hash)) throw new Exception("Unknown callback."); var weakCallback = callbacks[hash]; - JsCallback callback; - if (weakCallback.TryGetTarget(out callback)) + if (weakCallback.TryGetTarget(out JsCallback callback)) { TimedFunction(() => { @@ -44,8 +43,7 @@ public void HandleTopic(string hash, string data) internal override bool HandleException(Exception exception) { - var ex = exception as JsScriptException; - if (ex != null) + if (exception is JsScriptException ex) { terminal.PrintException(ex); return true; diff --git a/ByondLang/Interface/JsCallback.cs b/ByondLang/Interface/JsCallback.cs index 14d09cb..5ac2910 100644 --- a/ByondLang/Interface/JsCallback.cs +++ b/ByondLang/Interface/JsCallback.cs @@ -9,14 +9,16 @@ namespace ByondLang.Interface public class JsCallback : IDisposable { public JsValue CallbackFunction; + public BaseProgram program; public string Id; protected bool disposedValue; - public JsCallback(string id, JsValue callback) + public JsCallback(string id, JsValue callback, BaseProgram program) { CallbackFunction = callback; Id = id; callback.AddRef(); + this.program = program; } protected virtual void Dispose(bool disposing) @@ -27,7 +29,10 @@ protected virtual void Dispose(bool disposing) { // TODO: dispose managed state (managed objects) } - CallbackFunction.Release(); + program.Function(() => + { + CallbackFunction.Release(); + }, ChakraCore.JsTaskPriority.INITIALIZATION); // TODO: free unmanaged resources (unmanaged objects) and override finalizer // TODO: set large fields to null diff --git a/ByondLang/Interface/StateObjects/Communications.cs b/ByondLang/Interface/StateObjects/Communications.cs index 0b2c2ea..6d6e9bb 100644 --- a/ByondLang/Interface/StateObjects/Communications.cs +++ b/ByondLang/Interface/StateObjects/Communications.cs @@ -1,6 +1,5 @@ using ByondLang.ChakraCore.Hosting; using ByondLang.ChakraCore.Reflection; -using ByondLang.Models; using ByondLang.Services; using System; using System.Collections.Generic; @@ -12,9 +11,7 @@ namespace ByondLang.Interface.StateObjects [JsObject] public class Communications { - internal JsValue handler = JsValue.Invalid; private NTSL3Service service; - private List pendingSignals = new List(); public Communications(NTSL3Service service) @@ -25,52 +22,7 @@ public Communications(NTSL3Service service) [JsCallable] public void setSignalHandler(JsValue handler) { - var type = handler.ValueType; - if (type != JsValueType.Function && type != JsValueType.Null) - throw new Exception("Invalid paramter type."); - if(this.handler.IsValid) - this.handler.Release(); - if(type == JsValueType.Null) - { - this.handler = JsValue.Invalid; - return; - } - handler.AddRef(); - this.handler = handler; - } - - [JsCallable] - public TComSignal createSignal() - { - return new TComSignal(); - } - - [JsCallable] - public TComSignal broadcast() - { - var n = createSignal(); - AddSignalIfCan(n); - return n; - } - - [JsCallable] - public TComSignal broadcast(TComSignal signal) - { - AddSignalIfCan(signal); - return signal; - } - - private void AddSignalIfCan(TComSignal signal) - { - if (!pendingSignals.Contains(signal)) - pendingSignals.Add(signal); - } - - public TComSignal[] GetSignals() - { - var signals = pendingSignals.ToArray(); - pendingSignals.Clear(); - return signals; + throw new NotImplementedException(); } } } diff --git a/ByondLang/Interface/StateObjects/Terminal.cs b/ByondLang/Interface/StateObjects/Terminal.cs index acb254c..5ec913c 100644 --- a/ByondLang/Interface/StateObjects/Terminal.cs +++ b/ByondLang/Interface/StateObjects/Terminal.cs @@ -23,6 +23,7 @@ public class Terminal [JsMapped] public int height { get; private set; } = 20; TerminalChar[][] char_array; + object char_array_lock = new object(); public Color background = new Color(0, 0, 0); public Color foreground = new Color(255, 255, 255); @@ -75,20 +76,24 @@ public JsValue getCursor(TypeMapper tm) array.SetIndexedProperty(1, tm.MTS(cursorY)); return array; } - [JsCallable] + [JsCallable()] public void clear() { cursorX = 0; cursorY = 0; - char_array = new TerminalChar[height][]; - for (int y = 0; y < height; y++) + lock (char_array_lock) { - char_array[y] = new TerminalChar[width]; - for (int x = 0; x < width; x++) + char_array = new TerminalChar[height][]; + for (int y = 0; y < height; y++) { - char_array[y][x] = new TerminalChar(' ', background, foreground); + char_array[y] = new TerminalChar[width]; + for (int x = 0; x < width; x++) + { + char_array[y][x] = new TerminalChar(' ', background, foreground); + } } } + } [JsCallable] public void write(JsValue val) @@ -141,7 +146,7 @@ private void realWrite(string str, JsCallback topic = null, bool prompt = false) } else { - lock (char_array) + lock (char_array_lock) { char_array[cursorY][cursorX] = new TerminalChar(c, background, foreground, topic, prompt); } @@ -187,7 +192,7 @@ public void clearTopic(int x, int y, int w, int h) public string Stringify() { StringBuilder o = new StringBuilder(); - lock (char_array) + lock (char_array_lock) { for (int y = 0; y < char_array.Length; y++) { @@ -282,15 +287,18 @@ public void MoveDown() cursorY++; if (cursorY >= height) { - cursorY--; - for (int i = 0; i < height - 1; i++) + lock (char_array_lock) { - char_array[i] = char_array[i + 1]; - } - char_array[height - 1] = new TerminalChar[width]; - for (int x = 0; x < width; x++) - { - char_array[height - 1][x] = new TerminalChar(' ', background, foreground); + cursorY--; + for (int i = 0; i < height - 1; i++) + { + char_array[i] = char_array[i + 1]; + } + char_array[height - 1] = new TerminalChar[width]; + for (int x = 0; x < width; x++) + { + char_array[height - 1][x] = new TerminalChar(' ', background, foreground); + } } } } @@ -298,12 +306,15 @@ public void MoveDown() private void SetTopic(int x, int y, JsCallback callback, bool prompt) { - if (y >= 0 && x >= 0 && x < width && y < height) + lock (char_array_lock) { - char_array[y][x].callback = callback; - char_array[y][x].prompt = prompt; - + if (y >= 0 && x >= 0 && x < width && y < height) + { + char_array[y][x].callback = callback; + char_array[y][x].prompt = prompt; + } } + } internal void PrintException(Exception ex) diff --git a/ByondLang/Models/Event.cs b/ByondLang/Models/Event.cs new file mode 100644 index 0000000..dab4fad --- /dev/null +++ b/ByondLang/Models/Event.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace ByondLang.Models +{ + public class Event + { + + public Event(string eventId) + { + EventID = eventId; + } + + public string EventID { get; private set; } + virtual public EventType EventType => EventType.Unknown; + virtual public object EventData => null; + virtual public bool NeedsToBeResolved => false; + + virtual public void Resolve(object data) { } + virtual public void Reject(object data) { } + + } +} diff --git a/ByondLang/Models/EventDebugBreak.cs b/ByondLang/Models/EventDebugBreak.cs new file mode 100644 index 0000000..7b9f265 --- /dev/null +++ b/ByondLang/Models/EventDebugBreak.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace ByondLang.Models +{ + public class EventDebugBreak : Event + { + public EventDebugBreak(string eventId, ) : base(eventId) + { + + } + + public override EventType EventType => EventType.DebugBreak; + + public override object EventData => new { }; + + } +} diff --git a/ByondLang/Models/EventType.cs b/ByondLang/Models/EventType.cs new file mode 100644 index 0000000..909551e --- /dev/null +++ b/ByondLang/Models/EventType.cs @@ -0,0 +1,8 @@ +namespace ByondLang.Models +{ + public enum EventType + { + Unknown, + DebugBreak, + } +} \ No newline at end of file diff --git a/ByondLang/Program.cs b/ByondLang/Program.cs index f85f597..8c5f4ed 100644 --- a/ByondLang/Program.cs +++ b/ByondLang/Program.cs @@ -13,12 +13,6 @@ public class Program { public static void Main(string[] args) { - AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true); - if (args.Contains("--worker")) - { - CreateWorkerHostBuilder(args).Build().Run(); - return; - } CreateHostBuilder(args).Build().Run(); } @@ -32,24 +26,5 @@ public static IHostBuilder CreateHostBuilder(string[] args) => webBuilder.UseUrls("http://localhost:1945"); webBuilder.UseStartup(); }); - - public static IHostBuilder CreateWorkerHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseKestrel(options => - { - options.Limits.MaxRequestLineSize = (int)Math.Pow(2, 16); - }); - webBuilder.UseStartup(); - }) - .ConfigureWebHost(webBuilder => - { - webBuilder.UseKestrel(options => - { - var port = Convert.ToInt32( Environment.GetEnvironmentVariable("_WORKER_PORT")); - options.ListenLocalhost(port, o => o.Protocols = Microsoft.AspNetCore.Server.Kestrel.Core.HttpProtocols.Http2); - }); - }); } } diff --git a/ByondLang/Services/NTSL3Service.cs b/ByondLang/Services/NTSL3Service.cs index f48f22c..380af49 100644 --- a/ByondLang/Services/NTSL3Service.cs +++ b/ByondLang/Services/NTSL3Service.cs @@ -1,4 +1,4 @@ -using ByondLang.Api; +using ByondLang.Interface; using Microsoft.Extensions.Configuration; using System; using System.Collections.Generic; @@ -8,12 +8,10 @@ namespace ByondLang.Services { public class NTSL3Service { - private readonly Dictionary programs = new Dictionary(); + private readonly Dictionary programs = new Dictionary(); private int lastId = 1; public IConfiguration _config; private IServiceProvider serviceProvider; - private int nextPort = 10000; - private readonly Queue recycledPrograms = new Queue(); public NTSL3Service(IServiceProvider serviceProvider, IConfiguration configuration) { @@ -25,57 +23,42 @@ internal void Reset() { foreach (var program in programs) { - _ = recycle(program.Value); + program.Value.Dispose(); } programs.Clear(); lastId = 1; - nextPort = 10000; } private int GenerateNewId() => lastId++; - private int GenerateNewPort() => nextPort++; public async Task Execute(int id, string code) { await GetProgram(id).ExecuteScript(code); } - public State.IExecutionContext GetProgram(int id) + public BaseProgram GetProgram(int id) => GetProgram(id); + + public PType GetProgram(int id) where PType : BaseProgram { if (!programs.ContainsKey(id)) throw new ArgumentException("Provided ID is not found."); var p = programs[id]; - return p; - } - - private State.IExecutionContext obtainNewProgram() - { - if(recycledPrograms.Count > 0) - { - var p = recycledPrograms.Dequeue(); - p.Start(GenerateNewPort, serviceProvider); - return p; - } else - { - if (programs.Count + recycledPrograms.Count > _config.GetValue("MaxExecutors", 50)) - throw new Exception("Too many running programs."); - State.IExecutionContext p; - if (_config.GetValue("inProcess", false)) - p = new State.LocalExecutionContext(); - else - p = new State.RemoteExecutionContext(); - p.Start(GenerateNewPort, serviceProvider); - return p; - } + if (p is PType program) + return program; + else + throw new Exception("Tried to get wrong type of program."); } - internal int NewProgram(ProgramType programType) + internal int NewProgram(Func constructor) where PType : BaseProgram { - var program = obtainNewProgram(); + if (programs.Count > _config.GetValue("MaxExecutors", 50)) + throw new Exception("Too many programs."); + var program = constructor(serviceProvider); var id = GenerateNewId(); programs.Add(id, program); - program.InitializeProgram(programType); + _ = program.InitializeState(); + return id; } @@ -84,19 +67,7 @@ internal void Remove(int id) var p = GetProgram(id); programs.Remove(id); - _ = recycle(p); - } - - private async Task recycle(State.IExecutionContext program) - { - bool result = await program.Recycle(); - if(result) - { - recycledPrograms.Enqueue(program); - } else - { - program.Dispose(); - } + p.Dispose(); } } } diff --git a/ByondLang/Services/NTSL3StateService.cs b/ByondLang/Services/NTSL3StateService.cs deleted file mode 100644 index f3802fa..0000000 --- a/ByondLang/Services/NTSL3StateService.cs +++ /dev/null @@ -1,26 +0,0 @@ -using ByondLang.Api; -using ByondLang.Interface; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace ByondLang.Services -{ - public class NTSL3StateService: IDisposable - { - public ProgramType type = ProgramType.None; - public BaseProgram program = null; - public IServiceProvider serviceProvider = null; - - public NTSL3StateService(IServiceProvider serviceProvider) - { - this.serviceProvider = serviceProvider; - } - - public void Dispose() - { - program?.Dispose(); - } - } -} diff --git a/ByondLang/Services/ProgramService.cs b/ByondLang/Services/ProgramService.cs deleted file mode 100644 index e8163c0..0000000 --- a/ByondLang/Services/ProgramService.cs +++ /dev/null @@ -1,96 +0,0 @@ -using ByondLang.Api; -using ByondLang.ChakraCore.Hosting; -using ByondLang.Interface; -using Grpc.Core; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace ByondLang.Services -{ - public class ProgramService : Api.Program.ProgramBase - { - NTSL3StateService _state; - - public ProgramService(NTSL3StateService stateService) - { - _state = stateService; - } - - public override async Task status(StatusRequest request, ServerCallContext context) - { - if(_state.program == null) - { - if (request.Initialize) - { - switch (request.Type) - { - case ProgramType.None: - throw new Exception("Unspecified program type"); - case ProgramType.ComputerProgram: - _state.program = new ComputerProgram(_state.serviceProvider); - await _state.program.InitializeState(); - break; - default: - break; - } - } else { - return new StatusResponse() - { - Type = ProgramType.None - }; - } - } - var response = new StatusResponse() - { - Type = _state.type - }; - if(_state.program is ComputerProgram computerProgram) - { - response.Terminal = new TerminalState() { - Buffer = computerProgram.GetTerminalBuffer() - }; - } - return response; - } - - public override async Task execute(ExecuteRequest request, ServerCallContext context) - { - await _state.program?.ExecuteScript(request.Code); - return new VoidMessage(); - } - - public override Task handleTopic(TopicRequest request, ServerCallContext context) - { - if(_state.program is ComputerProgram computerProgram) - { - computerProgram.HandleTopic(request.TopicId, request.Data); - } - else - { - throw new Exception("Invalid type."); - } - return Task.FromResult(new VoidMessage()); - } - - public override Task recycle(VoidMessage request, ServerCallContext context) - { - _state.program?.Dispose(); - _state.program = null; - return Task.FromResult(request); - } - - public override Task setDebugingState(DebugingState request, ServerCallContext context) - { - - return Task.FromResult(new VoidMessage()); - } - - public void FullDispose() - { - _state.Dispose(); - _state = null; - } - } -} diff --git a/ByondLang/State/IExecutionContext.cs b/ByondLang/State/IExecutionContext.cs deleted file mode 100644 index b8a35ff..0000000 --- a/ByondLang/State/IExecutionContext.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace ByondLang.State -{ - public interface IExecutionContext : IDisposable - { - Task ExecuteScript(string script); - Task InitializeProgram(Api.ProgramType type); - Task GetBuffer(); - void Start(Func portGenerator, IServiceProvider serviceProvider); - Task HandleTopic(string topic, string data); - Task Recycle(); - Task SetDebuggingState(bool state); - } -} diff --git a/ByondLang/State/LocalExecutionContext.cs b/ByondLang/State/LocalExecutionContext.cs deleted file mode 100644 index fbc6d5d..0000000 --- a/ByondLang/State/LocalExecutionContext.cs +++ /dev/null @@ -1,77 +0,0 @@ -using ByondLang.Api; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace ByondLang.State -{ - public class LocalExecutionContext : IExecutionContext - { - Services.ProgramService programService; - Api.StatusResponse lastStatus; - - public void Dispose() - { - programService.FullDispose(); - } - - public async Task ExecuteScript(string script) - { - await programService.execute(new ExecuteRequest() - { - Code = script - }, null); - } - - public async Task GetBuffer() - { - try - { - lastStatus = await programService.status(new StatusRequest(), null); - return lastStatus?.Terminal?.Buffer; - } - catch (Exception) - { - return "                                                                
                                                                
                                                                
                                                                
                  Operating system has crashed.                 
                                                                
                                                                
                                                                
                                                                
                                                                
                                                                
                                                                
                                                                
                                                                
                                                                
                                                                
                                                                
                                                                
                                                                
                                                                
"; - } - } - - public async Task HandleTopic(string topic, string data) - { - await programService.handleTopic(new TopicRequest() - { - TopicId = topic, - Data = data - }, null); - } - - public async Task InitializeProgram(ProgramType type) - { - lastStatus = await programService.status(new StatusRequest() - { - Initialize = true, - Type = type, - }, null); - } - - public Task Recycle() - { - programService.recycle(null, null); - return Task.FromResult(true); - } - - public async Task SetDebuggingState(bool state) - { - await programService.setDebugingState(new DebugingState() - { - Enabled = state - }, null); - } - - public void Start(Func portGenerator, IServiceProvider serviceProvider) - { - programService = new Services.ProgramService(new Services.NTSL3StateService(serviceProvider)); - } - } -} diff --git a/ByondLang/State/RemoteExecutionContext.cs b/ByondLang/State/RemoteExecutionContext.cs deleted file mode 100644 index 80a3d85..0000000 --- a/ByondLang/State/RemoteExecutionContext.cs +++ /dev/null @@ -1,124 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using System.Diagnostics; -using System.IO; -using Grpc.Net.Client; -using System.Net.Http; -using ByondLang.Api; - -namespace ByondLang.State -{ - public class RemoteExecutionContext : IDisposable, IExecutionContext - { - Process process; - GrpcChannel channel; - Api.Program.ProgramClient client; - Api.StatusResponse lastStatus; - - public async Task ExecuteScript(string script) - { - await client.executeAsync(new Api.ExecuteRequest() - { - Code = script - }); - } - - public async Task InitializeProgram(Api.ProgramType type) - { - lastStatus = await client.statusAsync(new Api.StatusRequest() - { - Initialize = true, - Type = type, - }); - } - - public async Task GetBuffer() - { - try - { - lastStatus = await client.statusAsync(new Api.StatusRequest()); - return lastStatus?.Terminal?.Buffer; - } - catch (Exception) - { - return "                                                                
                                                                
                                                                
                                                                
                  Operating system has crashed.                 
                                                                
                                                                
                                                                
                                                                
                                                                
                                                                
                                                                
                                                                
                                                                
                                                                
                                                                
                                                                
                                                                
                                                                
                                                                
"; - } - } - - public void Dispose() - { - process?.Kill(true); - process?.Dispose(); - channel?.Dispose(); - } - - public void Start(Func portGenerator, IServiceProvider serviceProvider) - { - if(process != null) - { - if(process.HasExited) - { - channel.Dispose(); - spawn(portGenerator()); - } - } else - { - spawn(portGenerator()); - } - } - - private void spawn(int port) - { - var mp = Process.GetCurrentProcess(); - - var startInfo = new ProcessStartInfo(mp.MainModule.FileName, "--worker"); - startInfo.EnvironmentVariables["_WORKER_PORT"] = $"{port}"; - startInfo.UseShellExecute = false; - process = Process.Start(startInfo); - - var httpHandler = new HttpClientHandler(); - httpHandler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator; - - channel = GrpcChannel.ForAddress($"http://localhost:{port}", new GrpcChannelOptions() { - HttpHandler = httpHandler - }); - - client = new Api.Program.ProgramClient(channel); - } - - // Computer only - public async Task HandleTopic(string topic, string data) - { - await client.handleTopicAsync(new TopicRequest() { - TopicId = topic, - Data = data - });; - } - - public async Task Recycle() - { - try - { - await client.recycleAsync(new VoidMessage()); - lastStatus = null; - return true; - } - catch (Exception) - { - return false; - } - - } - - public async Task SetDebuggingState(bool state) - { - await client.setDebugingStateAsync(new DebugingState() - { - Enabled = state - }); - lastStatus = null; - } - } -} diff --git a/ByondLang/UnitTests/ChakraCore/TMTests.cs b/ByondLang/UnitTests/ChakraCore/TMTests.cs index 5ce2bfc..d8a184d 100644 --- a/ByondLang/UnitTests/ChakraCore/TMTests.cs +++ b/ByondLang/UnitTests/ChakraCore/TMTests.cs @@ -20,21 +20,20 @@ public TMTests(CCFixture cFixture) [Fact] public void BasicTypeTest() { - using (var tm = new ByondLang.ChakraCore.TypeMapper()) - { - using (new JsContext.Scope(fixture.context)) - { - var glob = JsValue.GlobalObject; - glob.SetProperty("AT", tm.MTS((Action)Assert.True), true); - glob.SetProperty("AF", tm.MTS((Action)Assert.False), true); - glob.SetProperty("tv_string1", tm.MTS("Test"), true); - glob.SetProperty("tv_string2", tm.MTS("Uhm"), true); - glob.SetProperty("tv_number1", tm.MTS(10), true); - glob.SetProperty("tv_number2", tm.MTS(1.5512), true); - var TestVal = new TMTestClass(); - glob.SetProperty("tv_obj", tm.MTS(TestVal), true); - - JsContext.RunScript(@" + using var tm = new ByondLang.ChakraCore.TypeMapper(); + using (new JsContext.Scope(fixture.context)) + { + var glob = JsValue.GlobalObject; + glob.SetProperty("AT", tm.MTS((Action)Assert.True), true); + glob.SetProperty("AF", tm.MTS((Action)Assert.False), true); + glob.SetProperty("tv_string1", tm.MTS("Test"), true); + glob.SetProperty("tv_string2", tm.MTS("Uhm"), true); + glob.SetProperty("tv_number1", tm.MTS(10), true); + glob.SetProperty("tv_number2", tm.MTS(1.5512), true); + var TestVal = new TMTestClass(); + glob.SetProperty("tv_obj", tm.MTS(TestVal), true); + + JsContext.RunScript(@" AT(tv_string1 == 'Test') AT(tv_string2 == 'Uhm') AT(tv_number1 == 10) @@ -43,15 +42,14 @@ public void BasicTypeTest() AT(tv_obj.Flag == 0) AT(tv_obj.TryTrue()) AF(tv_obj.false()) -"); - - TestVal.Flag += 1; - JsContext.RunScript(@" +"); + + TestVal.Flag += 1; + JsContext.RunScript(@" AT(tv_obj.Flag == 1) tv_obj.Flag = 12 -"); - Assert.Equal(12, TestVal.Flag); - } +"); + Assert.Equal(12, TestVal.Flag); } } } diff --git a/ByondLang/UnitTests/StateTests/RuntimeContextTests.cs b/ByondLang/UnitTests/StateTests/RuntimeContextTests.cs index 41f211d..7548e2c 100644 --- a/ByondLang/UnitTests/StateTests/RuntimeContextTests.cs +++ b/ByondLang/UnitTests/StateTests/RuntimeContextTests.cs @@ -12,13 +12,11 @@ public class RuntimeContextTests [Fact] public async void ConstructAndDispose() { - using (var program = new BaseProgram(null)) - { - await program.InitializeState(); - var result = program.ExecuteScript("Math.PI * (5+2)"); + using var program = new BaseProgram(null); + await program.InitializeState(); + var result = program.ExecuteScript("Math.PI * (5+2)"); - Assert.True(result.Wait(200)); - } + Assert.True(result.Wait(200)); } } } diff --git a/ByondLang/UnitTests/StateTests/RuntimeTaskSchedulerTests.cs b/ByondLang/UnitTests/StateTests/RuntimeTaskSchedulerTests.cs index 0ebdbab..14f776e 100644 --- a/ByondLang/UnitTests/StateTests/RuntimeTaskSchedulerTests.cs +++ b/ByondLang/UnitTests/StateTests/RuntimeTaskSchedulerTests.cs @@ -52,5 +52,29 @@ public void ProperPriority() Assert.False(flag2); // And we expect task2 to not have finished yeat. } + + [Fact] + public void BreakState() + { + var s = new JsPFIFOScheduler(); + bool flag1 = false; + bool flag2 = false; + bool flag3 = false; + var task1 = s.Run(() => { s.EnterBreakState(); flag1 = true; }); + var task2 = s.Run(() => { flag3 = true; }); + var task3 = s.RunDebug(() => { flag2 = true; }); + task3.Wait(); + Assert.False(flag1); + Assert.True(flag2); + Assert.False(flag3); + Assert.False(task1.IsCompleted); + Assert.False(task2.IsCompleted); + var task4 = s.RunDebug(() => { s.ExitBreakState(); }); + task4.Wait(); + task1.Wait(); + Assert.True(flag1); + Assert.True(flag2); + Assert.True(flag3); + } } } diff --git a/ByondLang/WorkerStartup.cs b/ByondLang/WorkerStartup.cs deleted file mode 100644 index 06f82cc..0000000 --- a/ByondLang/WorkerStartup.cs +++ /dev/null @@ -1,42 +0,0 @@ -using ByondLang.Services; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Routing; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; - -namespace ByondLang -{ - public class WorkerStartup - { - // This method gets called by the runtime. Use this method to add services to the container. - // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 - public void ConfigureServices(IServiceCollection services) - { - services.AddGrpc(); - services.AddSingleton(); - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - - app.UseRouting(); - - app.UseEndpoints(endpoints => - { - endpoints.MapGrpcService(); - - endpoints.MapGet("/", async context => - { - await context.Response.WriteAsync("Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909"); - }); - }); - } - } -} diff --git a/ByondLang/wwwroot/index.htm b/ByondLang/wwwroot/index.htm index fc28403..30413bb 100644 --- a/ByondLang/wwwroot/index.htm +++ b/ByondLang/wwwroot/index.htm @@ -33,52 +33,63 @@ -
- - {{ alert.message }} - - - - - - Computer program - - - - - - Reset daemon - -
- -
-

Options:

- -
-
- - -
- +
+ +
+

Options:

+ +
+ + + +
+ + + + Events go here + + +
diff --git a/ByondLang/wwwroot/js/app.js b/ByondLang/wwwroot/js/app.js index 7fb09e4..2c4f147 100644 --- a/ByondLang/wwwroot/js/app.js +++ b/ByondLang/wwwroot/js/app.js @@ -51,25 +51,20 @@ const to = { props: ["to"], methods: { invoke() { - if (programId == null) return; + if (this.$root.programId == null) return; var topic = this.to; var data = ""; if (this.to[0] == "?") { topic = this.to.substring(1); data = prompt("Please enter input...", ""); } - $.ajax({ - type: "GET", - url: `/computer/topic`, - data: { - id: programId, + axios.get("/computer/topic", { + params: { + id: this.$root.programId, topic: topic, data: data, - }, - success: (data) => { - get_buffer(); - }, - }); + } + }) }, }, template: @@ -166,6 +161,22 @@ var app = new Vue({ this.a('error', 'Failed to update console buffer.') }) }, + set_debugging(state = 1) { + axios.get("/debug/set", { + params: { + id: this.programId, + state: state + } + }).then(r => { + if(state) + this.a('success', 'Enabled debugging.') + else + this.a('success', 'Disabled debugging.') + }) + .catch(() => { + this.a('error', 'Failed to change debugging state.') + }) + }, remove() { if (this.programId == null) return axios.get("/remove", {