diff --git a/EasyCommands.Tests/EasyCommands.Tests.csproj b/EasyCommands.Tests/EasyCommands.Tests.csproj
index a614f0f..15ee39a 100644
--- a/EasyCommands.Tests/EasyCommands.Tests.csproj
+++ b/EasyCommands.Tests/EasyCommands.Tests.csproj
@@ -144,6 +144,7 @@
+
diff --git a/EasyCommands.Tests/ScriptTests/FunctionalTests/BlockHandlers/ThreadBlockTests.cs b/EasyCommands.Tests/ScriptTests/FunctionalTests/BlockHandlers/ThreadBlockTests.cs
new file mode 100644
index 0000000..166c08b
--- /dev/null
+++ b/EasyCommands.Tests/ScriptTests/FunctionalTests/BlockHandlers/ThreadBlockTests.cs
@@ -0,0 +1,276 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using System;
+using Moq;
+using Sandbox.ModAPI.Ingame;
+using static EasyCommands.Tests.ScriptTests.MockEntityUtility;
+
+namespace EasyCommands.Tests.ScriptTests {
+ [TestClass]
+ public class ThreadBlockTests {
+
+ [TestMethod]
+ public void GetCurrentThreadNameWhenNotCustom() {
+ using (ScriptTest test = new ScriptTest(@"print the current thread name")) {
+ test.program.logLevel = IngameScript.Program.LogLevel.SCRIPT_ONLY;
+ test.RunUntilDone();
+
+ Assert.AreEqual(1, test.Logger.Count);
+ Assert.AreEqual("main", test.Logger[0]);
+ }
+ }
+
+ [TestMethod]
+ public void GetCurrentThreadNameWhenCustom() {
+ using (ScriptTest test = new ScriptTest(@"
+set the current thread name to ""Hello World!""
+print the current thread name
+")) {
+ test.program.logLevel = IngameScript.Program.LogLevel.SCRIPT_ONLY;
+ test.RunUntilDone();
+
+ Assert.AreEqual(1, test.Logger.Count);
+ Assert.AreEqual("Hello World!", test.Logger[0]);
+ }
+ }
+
+ [TestMethod]
+ public void GetCurrentThreadNameForAnonymousAsyncThreadWhenNotCustom() {
+ using (ScriptTest test = new ScriptTest(@"
+async
+ print the current thread name
+wait 1
+")) {
+ test.program.logLevel = IngameScript.Program.LogLevel.SCRIPT_ONLY;
+ test.RunUntilDone();
+
+ Assert.AreEqual(1, test.Logger.Count);
+ Assert.AreEqual("Unknown", test.Logger[0]);
+ }
+ }
+
+ [TestMethod]
+ public void GetCurrentThreadNameForAsyncThreadWhenNotCustom() {
+ using (ScriptTest test = new ScriptTest(@"
+async doThing
+wait 1
+
+:doThing
+print the current thread name
+")) {
+ test.program.logLevel = IngameScript.Program.LogLevel.SCRIPT_ONLY;
+ test.RunUntilDone();
+
+ Assert.AreEqual(1, test.Logger.Count);
+ Assert.AreEqual("doThing", test.Logger[0]);
+ }
+ }
+
+ [TestMethod]
+ public void GetCurrentThreadNameForAsyncThreadWhenCustom() {
+ using (ScriptTest test = new ScriptTest(@"
+async
+ set the current thread name to ""Hello World!""
+ print the current thread name
+wait 1
+")) {
+ test.program.logLevel = IngameScript.Program.LogLevel.SCRIPT_ONLY;
+ test.RunUntilDone();
+
+ Assert.AreEqual(1, test.Logger.Count);
+ Assert.AreEqual("Hello World!", test.Logger[0]);
+ }
+ }
+
+ [TestMethod]
+ public void GetAllThreadNames() {
+ using (ScriptTest test = new ScriptTest(@"
+
+queue doThing
+async
+ set the current thread name to ""Async Thread""
+ wait 0.25
+async
+ set the current thread name to ""Async Thread 2""
+ wait 1
+set the current thread name to ""My Main""
+wait
+print all thread names
+
+:doThing
+print all thread names
+wait 0.5
+print all thread names
+")) {
+ test.RunUntilDone();
+
+ Assert.AreEqual(3, test.Logger.Count);
+ Assert.AreEqual("[\"Async Thread\",\"Async Thread 2\",doThing,\"My Main\"]", test.Logger[0]);
+ Assert.AreEqual("[\"Async Thread\",\"Async Thread 2\",doThing]", test.Logger[1]);
+ Assert.AreEqual("[\"Async Thread 2\",doThing]", test.Logger[2]);
+ }
+ }
+
+ [TestMethod]
+ public void GetAsyncThreadNames() {
+ using (ScriptTest test = new ScriptTest(@"
+queue doThing
+print the list of async threads
+async
+ set the current thread name to ""Async Thread""
+ wait 0.25
+print the list of async threads
+async
+ set the current thread name to ""Async Thread 2""
+ wait 1
+print the list of async threads
+
+:doThing
+print the list of async threads
+wait 0.5
+print the list of async threads
+")) {
+ test.RunUntilDone();
+
+ Assert.AreEqual(5, test.Logger.Count);
+ Assert.AreEqual("[]", test.Logger[0]);
+ Assert.AreEqual("[Unknown]", test.Logger[1]);
+ Assert.AreEqual("[Unknown,Unknown]", test.Logger[2]);
+ Assert.AreEqual("[\"Async Thread\",\"Async Thread 2\"]", test.Logger[3]);
+ Assert.AreEqual("[\"Async Thread 2\"]", test.Logger[4]);
+ }
+ }
+
+ [TestMethod]
+ public void GetChildThreadNames() {
+ using (ScriptTest test = new ScriptTest(@"
+print the list of child threads
+async
+ set the current thread name to ""Async Thread""
+ async
+ set the current thread name to ""Async Thread 2""
+ wait 1
+ print the list of child threads
+ wait 0.25
+ print the list of child threads
+wait 2 ticks
+print the list of child threads
+")) {
+ test.RunUntilDone();
+
+ Assert.AreEqual(4, test.Logger.Count);
+ Assert.AreEqual("[]", test.Logger[0]);
+ Assert.AreEqual("[\"Async Thread\"]", test.Logger[1]);
+ Assert.AreEqual("[\"Async Thread 2\"]", test.Logger[2]);
+ Assert.AreEqual("[]", test.Logger[3]);
+ }
+ }
+
+ [TestMethod]
+ public void GetQueuedThreadNames() {
+ using (ScriptTest test = new ScriptTest(@"
+print the list of queued threads
+queue doThing
+print the list of queued threads
+queue doThing2
+print the list of queued threads
+
+:doThing
+print the list of queued threads
+
+:doThing2
+print the list of queued threads
+")) {
+ test.RunUntilDone();
+
+ Assert.AreEqual(5, test.Logger.Count);
+ Assert.AreEqual("[]", test.Logger[0]);
+ Assert.AreEqual("[doThing]", test.Logger[1]);
+ Assert.AreEqual("[doThing,doThing2]", test.Logger[2]);
+ Assert.AreEqual("[doThing2]", test.Logger[3]);
+ Assert.AreEqual("[]", test.Logger[4]);
+ }
+ }
+
+ [TestMethod]
+ public void TerminateCurrentThread() {
+ using (ScriptTest test = new ScriptTest(@"
+Print ""Beginning Thread""
+terminate the current thread
+print ""Done""
+")) {
+ test.RunUntilDone();
+
+ Assert.AreEqual(1, test.Logger.Count);
+ Assert.AreEqual("Beginning Thread", test.Logger[0]);
+ }
+ }
+
+ [TestMethod]
+ public void TerminateQueuedThreadsDoesNotTermiateCurrentThread() {
+ using (ScriptTest test = new ScriptTest(@"
+Print ""Calling Queue""
+callQueue 4 times
+terminate all queued threads
+wait 1
+print ""Done""
+
+:callQueue
+queue
+ print ""You cant kill me""
+")) {
+ test.RunUntilDone();
+
+ Assert.AreEqual(2, test.Logger.Count);
+ Assert.AreEqual("Calling Queue", test.Logger[0]);
+ Assert.AreEqual("Done", test.Logger[1]);
+ }
+ }
+
+ [TestMethod]
+ public void TerminateAsynchronousThreads() {
+ using (ScriptTest test = new ScriptTest(@"
+Print ""Calling Async""
+callAsync 4 times
+terminate all async threads
+print ""Done""
+
+:callAsync
+async
+ wait 1
+ print ""You cant kill me""
+")) {
+ test.RunUntilDone();
+
+ Assert.AreEqual(2, test.Logger.Count);
+ Assert.AreEqual("Calling Async", test.Logger[0]);
+ Assert.AreEqual("Done", test.Logger[1]);
+ }
+ }
+
+ [TestMethod]
+ public void TerminateChildThreadsDoesNotTerminateOtherAsyncThreads() {
+ using (ScriptTest test = new ScriptTest(@"
+Print ""Calling Async""
+callAsync
+wait 0.25
+cancel all child threads
+print ""Done""
+
+:callAsync
+async
+ async
+ wait 1
+ print ""This One Remains""
+ wait 1
+ print ""You cant kill me""
+")) {
+ test.RunUntilDone();
+
+ Assert.AreEqual(3, test.Logger.Count);
+ Assert.AreEqual("Calling Async", test.Logger[0]);
+ Assert.AreEqual("Done", test.Logger[1]);
+ Assert.AreEqual("This One Remains", test.Logger[2]);
+ }
+ }
+ }
+}
diff --git a/EasyCommands.Tests/ScriptTests/FunctionalTests/MultiThreadingTests.cs b/EasyCommands.Tests/ScriptTests/FunctionalTests/MultiThreadingTests.cs
index 551a5ed..36f7ee8 100644
--- a/EasyCommands.Tests/ScriptTests/FunctionalTests/MultiThreadingTests.cs
+++ b/EasyCommands.Tests/ScriptTests/FunctionalTests/MultiThreadingTests.cs
@@ -1,5 +1,7 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
+using System.Collections.Generic;
+using System.Linq;
using static IngameScript.Program;
namespace EasyCommands.Tests.ScriptTests {
@@ -339,5 +341,233 @@ async call runAsync
}
}
+ [TestMethod]
+ public void asyncVariableIsNotAffectedByMainThread() {
+ String script = @"
+:main
+set a to 1
+async call runAsync
+set a to 2
+
+:runAsync
+print 'Variable is: ' + a
+wait 1
+print 'Variable is: ' + a
+";
+
+ using (var test = new ScriptTest(script)) {
+ test.program.logLevel = LogLevel.SCRIPT_ONLY;
+ test.RunUntilDone();
+
+ Assert.AreEqual(2, test.Logger.Count);
+ Assert.AreEqual("Variable is: 1", test.Logger[0]);
+ Assert.AreEqual("Variable is: 1", test.Logger[1]);
+ }
+ }
+
+ [TestMethod]
+ public void awaitCommandExecutesButBlocksOnAsyncThreads() {
+ String script = @"
+:main
+await
+ async
+ Print ""Async Thread""
+ wait 1
+ Print ""Async Thread Done""
+ print ""Main Thread""
+ async
+ Print ""Async Thread 2""
+ wait 0.5
+ Print ""Async Thread 2 Done""
+print ""Main Thread Done""
+";
+
+ using (var test = new ScriptTest(script)) {
+ test.program.logLevel = LogLevel.SCRIPT_ONLY;
+ test.RunUntilDone();
+
+ Assert.AreEqual(6, test.Logger.Count);
+ Assert.AreEqual("Main Thread", test.Logger[0]);
+ Assert.AreEqual("Async Thread", test.Logger[1]);
+ Assert.AreEqual("Async Thread 2", test.Logger[2]);
+ Assert.AreEqual("Async Thread 2 Done", test.Logger[3]);
+ Assert.AreEqual("Async Thread Done", test.Logger[4]);
+ Assert.AreEqual("Main Thread Done", test.Logger[5]);
+ }
+ }
+
+ [TestMethod]
+ public void awaitCommandWaitsForAsyncThreadSpawnedInSubFunction() {
+ String script = @"
+:main
+await
+ callAsync ""Async Thread"" 1
+ print ""Main Thread""
+ callAsync ""Async Thread 2"" 0.5
+print ""Main Thread Done""
+
+:callAsync threadName timeout
+async
+ Print threadName
+ wait timeout
+ Print threadName + "" Done""
+";
+
+ using (var test = new ScriptTest(script)) {
+ test.program.logLevel = LogLevel.SCRIPT_ONLY;
+ test.RunUntilDone();
+
+ Assert.AreEqual(6, test.Logger.Count);
+ Assert.AreEqual("Main Thread", test.Logger[0]);
+ Assert.AreEqual("Async Thread", test.Logger[1]);
+ Assert.AreEqual("Async Thread 2", test.Logger[2]);
+ Assert.AreEqual("Async Thread 2 Done", test.Logger[3]);
+ Assert.AreEqual("Async Thread Done", test.Logger[4]);
+ Assert.AreEqual("Main Thread Done", test.Logger[5]);
+ }
+ }
+
+ [TestMethod]
+ public void nestedAwaitCommandsProperlyBlocks() {
+ String script = @"
+:main
+await
+ callAsync ""Async Thread 2"" 1
+ await
+ callAsync ""Async Thread"" 0.5
+ print ""Main Thread""
+print ""Main Thread Done""
+
+:callAsync threadName timeout
+async
+ Print threadName
+ wait timeout
+ Print threadName + "" Done""
+";
+
+ using (var test = new ScriptTest(script)) {
+ test.program.logLevel = LogLevel.SCRIPT_ONLY;
+ test.RunUntilDone();
+
+ Assert.AreEqual(6, test.Logger.Count);
+ Assert.AreEqual("Async Thread 2", test.Logger[0]);
+ Assert.AreEqual("Async Thread", test.Logger[1]);
+ Assert.AreEqual("Async Thread Done", test.Logger[2]);
+ Assert.AreEqual("Main Thread", test.Logger[3]);
+ Assert.AreEqual("Async Thread 2 Done", test.Logger[4]);
+ Assert.AreEqual("Main Thread Done", test.Logger[5]);
+ }
+ }
+
+ [TestMethod]
+ public void awaitCommandCanBeBrokenOutOf() {
+ String script = @"
+:main
+await
+ print ""Main Thread""
+ async
+ Print ""Async Thread""
+ wait 1
+ Print ""Async Thread Done""
+ wait 0.5
+ break
+print ""Main Thread Done""
+
+";
+
+ using (var test = new ScriptTest(script)) {
+ test.program.logLevel = LogLevel.SCRIPT_ONLY;
+ test.RunUntilDone();
+
+ Assert.AreEqual(4, test.Logger.Count);
+ Assert.AreEqual("Main Thread", test.Logger[0]);
+ Assert.AreEqual("Async Thread", test.Logger[1]);
+ Assert.AreEqual("Main Thread Done", test.Logger[2]);
+ Assert.AreEqual("Async Thread Done", test.Logger[3]);
+ }
+ }
+
+ [TestMethod]
+ public void awaitCommandContinueActsAsBreak() {
+ String script = @"
+:main
+await
+ print ""Main Thread""
+ async
+ Print ""Async Thread""
+ wait 1
+ Print ""Async Thread Done""
+ wait 0.5
+ continue
+print ""Main Thread Done""
+
+";
+
+ using (var test = new ScriptTest(script)) {
+ test.program.logLevel = LogLevel.SCRIPT_ONLY;
+ test.RunUntilDone();
+
+ Assert.AreEqual(4, test.Logger.Count);
+ Assert.AreEqual("Main Thread", test.Logger[0]);
+ Assert.AreEqual("Async Thread", test.Logger[1]);
+ Assert.AreEqual("Main Thread Done", test.Logger[2]);
+ Assert.AreEqual("Async Thread Done", test.Logger[3]);
+ }
+ }
+
+ [TestMethod]
+ public void awaitCommandResetsCorrectly() {
+ String script = @"
+:main
+await
+ async
+ Print ""Async Thread""
+ Print ""Async Thread Done""
+print ""Main Thread Done""
+";
+
+ using (var test = new ScriptTest(script)) {
+ test.program.logLevel = LogLevel.SCRIPT_ONLY;
+ test.RunUntilDone();
+
+ Assert.AreEqual(3, test.Logger.Count);
+ Assert.AreEqual("Async Thread", test.Logger[0]);
+ Assert.AreEqual("Async Thread Done", test.Logger[1]);
+ Assert.AreEqual("Main Thread Done", test.Logger[2]);
+
+ test.Logger.Clear();
+ test.RunUntilDone();
+
+ Assert.AreEqual(3, test.Logger.Count);
+ Assert.AreEqual("Async Thread", test.Logger[0]);
+ Assert.AreEqual("Async Thread Done", test.Logger[1]);
+ Assert.AreEqual("Main Thread Done", test.Logger[2]);
+ }
+ }
+
+
+ [TestMethod]
+ public void terminateAllThreadsFromAsyncThread() {
+ String script = @"
+await
+ async
+ set the current thread name to ""My Async Thread""
+ wait 1.25
+ print ""Terminating Threads!""
+ terminate all threads
+ wait 1
+print ""Done!""
+";
+
+ using (var test = new ScriptTest(script)) {
+ test.program.logLevel = LogLevel.SCRIPT_ONLY;
+ test.RunUntilDone();
+
+ Assert.AreEqual(1, test.Logger.Count);
+ Assert.AreEqual("Terminating Threads!", test.Logger[0]);
+ }
+ }
+
+
}
}
diff --git a/EasyCommands/BlockHandlers/BlockHandlerRegistry.cs b/EasyCommands/BlockHandlers/BlockHandlerRegistry.cs
index 4e3fe53..a154813 100644
--- a/EasyCommands/BlockHandlers/BlockHandlerRegistry.cs
+++ b/EasyCommands/BlockHandlers/BlockHandlerRegistry.cs
@@ -76,6 +76,7 @@ public static class BlockHandlerRegistry {
{ Block.TANK, new GasTankBlockHandler() },
{ Block.TERMINAL, new TerminalBlockHandler() },
{ Block.TIMER, new TimerBlockHandler() },
+ { Block.THREAD, new ThreadBlockHandler() },
{ Block.THRUSTER, new ThrusterBlockHandler()},
{ Block.TURBINE, new EngineBlockHandler("WindTurbine") },
{ Block.TURRET, new TurretBlockHandler()},
@@ -95,6 +96,14 @@ public static List