From eb6e1dc91f30088d12af61ad4ad36ee4baa235c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Franc=C3=A9s?= Date: Mon, 1 Apr 2024 23:32:46 +0200 Subject: [PATCH] feat: delay instruction --- .../CustomData/Resources.Designer.cs | 451 +++++++++--------- .../CustomData/Resources.resx | 3 + .../CustomData/TestDelay.txt | 5 + .../CustomData/TestHydrogen.txt | 2 +- .../InstructionParserTest.cs | 38 ++ .../SequentialScript.Test.csproj | 1 + SequentialScript/Instructions.readme | 2 +- .../Instructions/InstructionParser.cs | 109 +++-- SequentialScript/MDK/MDK.options.props | 4 +- SequentialScript/Program.cs | 13 +- SequentialScript/SequentialScript.csproj | 3 + SequentialScript/Tasks/ITaskAction.cs | 52 ++ SequentialScript/Tasks/Task.cs | 2 +- SequentialScript/Tasks/TaskAction.cs | 71 ++- SequentialScript/Tasks/TaskActionDelay.cs | 58 +++ SequentialScript/Tasks/TaskEnums.cs | 44 ++ SequentialScript/Tasks/Tasks.cs | 224 ++++----- docs/README.md | 17 + 18 files changed, 727 insertions(+), 372 deletions(-) create mode 100644 SequentialScript.Test/CustomData/TestDelay.txt create mode 100644 SequentialScript/Tasks/ITaskAction.cs create mode 100644 SequentialScript/Tasks/TaskActionDelay.cs create mode 100644 SequentialScript/Tasks/TaskEnums.cs diff --git a/SequentialScript.Test/CustomData/Resources.Designer.cs b/SequentialScript.Test/CustomData/Resources.Designer.cs index 53b2844..3045bbe 100644 --- a/SequentialScript.Test/CustomData/Resources.Designer.cs +++ b/SequentialScript.Test/CustomData/Resources.Designer.cs @@ -1,214 +1,237 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace SequentialScript.Test.CustomData { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("SequentialScript.Test.CustomData.Resources", typeof(Resources).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to [OPEN] - ///run - /// Block -> Some Action /NoCheck /Key1:Value /Key2:"Value with spaces" /Key3:"Value with special characters :/" - ///as @action. - /// - internal static string TestArguments { - get { - return ResourceManager.GetString("TestArguments", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to [OPEN] - /// - ///// Comment over block - ///run // Comment in block - /// - /// // Comment over instruction - /// Door -> Open // Comment in instruction - /// - ///as @open_door // Comment at the end of the block. - /// - internal static string TestComments { - get { - return ResourceManager.GetString("TestComments", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to [OPEN] - ///run - /// Door Light -> Enable - ///as @open_door_1 - /// - ///when @open_door_1 - ///run - /// Door -> Open - ///as @open_door_2 - /// - ///when @open_door_1, @open_door_2 - ///run - /// Door Light -> Disable - ///as @open_door_3. - /// - internal static string TestDependences { - get { - return ResourceManager.GetString("TestDependences", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to [OPEN] - ///run - /// Door Light -> Enable - ///as @open_door_1 - /// - ///when @open_door_1, @open_door_3 - ///run - /// Door -> Open - ///as @open_door_2 - /// - ///when @open_door_1, @open_door_2 - ///run - /// Door Light -> Disable - ///as @open_door_3. - /// - internal static string TestDependences_Recursivity { - get { - return ResourceManager.GetString("TestDependences_Recursivity", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to [HYDROGEN_STOCKPILE] - ///run - /// Hydrogen Example (Thruster) -> Disable - ///as @thrusters_disabled - /// - ///run - /// Hydrogen Example (Status LCD) -> Set /Index:-1 /Background:YELLOW /Text:Connecting... - /// Hydrogen Example (Piston) -> Extend - ///as piston_extended - /// - ///when @piston_extended - ///run - /// Hydrogen Example (Connector) -> Connect - ///as @connector_locked - /// - ///when @connector_locked - ///run - /// Hydrogen Example (Status LCD) -> Set /Index:-1 /Background:BLUE /Text:Recharging... - /// Hydrogen Example (Tank) -> Stockpile - ///as @tank_full - /// - /// [rest of string was truncated]";. - /// - internal static string TestHydrogen { - get { - return ResourceManager.GetString("TestHydrogen", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to [OPEN] - ///run - /// Door -> Open - ///as @open_door. - /// - internal static string TestSimple { - get { - return ResourceManager.GetString("TestSimple", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to . - /// - internal static string TestSyntaxAliasDuplicated { - get { - return ResourceManager.GetString("TestSyntaxAliasDuplicated", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to [TEST] - ///run - /// Air Vent -> Depressurize - ///as @room_depressurized - /// - ///when @room_depressurized - ///run - /// Door 1 -> Enable - /// Door 1 -> Open - ///as door_opened. - /// - internal static string TestSyntaxAliasInvalid { - get { - return ResourceManager.GetString("TestSyntaxAliasInvalid", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to . - /// - internal static string TestSyntaxInstructionInvalid { - get { - return ResourceManager.GetString("TestSyntaxInstructionInvalid", resourceCulture); - } - } - } -} +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace SequentialScript.Test.CustomData { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("SequentialScript.Test.CustomData.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to [OPEN] + ///run + /// Block -> Some Action /NoCheck /Key1:Value /Key2:"Value with spaces" /Key3:"Value with special characters :/" + ///as @action. + /// + internal static string TestArguments { + get { + return ResourceManager.GetString("TestArguments", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to [OPEN] + /// + ///// Comment over block + ///run // Comment in block + /// + /// // Comment over instruction + /// Door -> Open // Comment in instruction + /// + ///as @open_door // Comment at the end of the block. + /// + internal static string TestComments { + get { + return ResourceManager.GetString("TestComments", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to [OPEN] + ///run + /// Block -> Some Action /NoCheck /Key1:Value /Key2:"Value with spaces" /Key3:"Value with special characters :/" + /// Delay 3000 + ///as @action. + /// + internal static string TestDelay { + get { + return ResourceManager.GetString("TestDelay", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to [OPEN] + ///run + /// Door Light -> Enable + ///as @open_door_1 + /// + ///when @open_door_1 + ///run + /// Door -> Open + ///as @open_door_2 + /// + ///when @open_door_1, @open_door_2 + ///run + /// Door Light -> Disable + ///as @open_door_3. + /// + internal static string TestDependences { + get { + return ResourceManager.GetString("TestDependences", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to [OPEN] + ///run + /// Door Light -> Enable + ///as @open_door_1 + /// + ///when @open_door_1, @open_door_3 + ///run + /// Door -> Open + ///as @open_door_2 + /// + ///when @open_door_1, @open_door_2 + ///run + /// Door Light -> Disable + ///as @open_door_3. + /// + internal static string TestDependences_Recursivity { + get { + return ResourceManager.GetString("TestDependences_Recursivity", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to [HYDROGEN_STOCKPILE] + ///run + /// Hydrogen Example (Thruster) -> Disable + ///as @thrusters_disabled + /// + ///run + /// Hydrogen Example (Status LCD) -> Set /Index:-1 /Background:YELLOW /Text:Connecting... + /// Hydrogen Example (Piston) -> Extend + ///as @piston_extended + /// + ///when @piston_extended + ///run + /// Hydrogen Example (Connector) -> Connect + ///as @connector_locked + /// + ///when @connector_locked + ///run + /// Hydrogen Example (Status LCD) -> Set /Index:-1 /Background:BLUE /Text:Recharging... + /// Hydrogen Example (Tank) -> Stockpile + ///as @tank_full + /// /// [rest of string was truncated]";. + /// + internal static string TestHydrogen { + get { + return ResourceManager.GetString("TestHydrogen", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to [OPEN] + ///run + /// Door -> Open + ///as @open_door. + /// + internal static string TestSimple { + get { + return ResourceManager.GetString("TestSimple", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to [TEST] + ///run + /// Block 1 -> Enabled + /// Block 2 -> Enabled + ///as @block_name + /// + ///run + /// Block 3 -> Enabled + /// Block 4 -> Enabled + /// Block 5 -> Enabled + ///as @block_name + ///. + /// + internal static string TestSyntaxAliasDuplicated { + get { + return ResourceManager.GetString("TestSyntaxAliasDuplicated", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to [TEST] + ///run + /// Air Vent -> Depressurize + ///as @room_depressurized + /// + ///when @room_depressurized + ///run + /// Door 1 -> Enable + /// Door 1 -> Open + ///as door_opened. + /// + internal static string TestSyntaxAliasInvalid { + get { + return ResourceManager.GetString("TestSyntaxAliasInvalid", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to . + /// + internal static string TestSyntaxInstructionInvalid { + get { + return ResourceManager.GetString("TestSyntaxInstructionInvalid", resourceCulture); + } + } + } +} diff --git a/SequentialScript.Test/CustomData/Resources.resx b/SequentialScript.Test/CustomData/Resources.resx index 52bc585..d5829f7 100644 --- a/SequentialScript.Test/CustomData/Resources.resx +++ b/SequentialScript.Test/CustomData/Resources.resx @@ -124,6 +124,9 @@ TestComments.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 + + TestDelay.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 + TestDependences.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 diff --git a/SequentialScript.Test/CustomData/TestDelay.txt b/SequentialScript.Test/CustomData/TestDelay.txt new file mode 100644 index 0000000..d602e50 --- /dev/null +++ b/SequentialScript.Test/CustomData/TestDelay.txt @@ -0,0 +1,5 @@ +[OPEN] +run + Block -> Some Action /NoCheck /Key1:Value /Key2:"Value with spaces" /Key3:"Value with special characters :/" + Delay 3000 +as @action \ No newline at end of file diff --git a/SequentialScript.Test/CustomData/TestHydrogen.txt b/SequentialScript.Test/CustomData/TestHydrogen.txt index 14e4c30..988fce2 100644 --- a/SequentialScript.Test/CustomData/TestHydrogen.txt +++ b/SequentialScript.Test/CustomData/TestHydrogen.txt @@ -6,7 +6,7 @@ as @thrusters_disabled run Hydrogen Example (Status LCD) -> Set /Index:-1 /Background:YELLOW /Text:Connecting... Hydrogen Example (Piston) -> Extend -as piston_extended +as @piston_extended when @piston_extended run diff --git a/SequentialScript.Test/InstructionParserTest.cs b/SequentialScript.Test/InstructionParserTest.cs index 26fb342..e732990 100644 --- a/SequentialScript.Test/InstructionParserTest.cs +++ b/SequentialScript.Test/InstructionParserTest.cs @@ -192,5 +192,43 @@ public void TestHydrogen() commands = IngameScript.InstructionParser.Parse(CustomData.Resources.TestHydrogen); commands.ToString(); } + + [TestMethod] + public void TestDelay() + { + IDictionary commands; + var expected = new Dictionary() + { + { "OPEN", new IngameScript.InstructionCommand { + CommandName = "OPEN", + Body = new Dictionary { + { "@action", new IngameScript.InstructionBlock { + Alias = "@action", + PreviousAlias = new string[] { }, + Instructions = new []{ new IngameScript.Instruction { + BlockName = "Block", + ActionName = "Some Action", + Arguments = new Dictionary { + { "NoCheck", "" }, + { "Key1", "Value" }, + { "Key2", "Value with spaces" }, + { "Key3", "Value with special characters :/" } + }, + IsValid = true, + }, new IngameScript.Instruction { + BlockName = null, + ActionName = "DELAY", + Arguments = new Dictionary { { "TIME", "3000" } }, + IsValid = true, + }}, + } } + }.Values + }} + }; + + commands = IngameScript.InstructionParser.Parse(CustomData.Resources.TestDelay); + InstrucionAssert.AreEqual(expected, commands); + } + } } diff --git a/SequentialScript.Test/SequentialScript.Test.csproj b/SequentialScript.Test/SequentialScript.Test.csproj index 5e55882..d29288c 100644 --- a/SequentialScript.Test/SequentialScript.Test.csproj +++ b/SequentialScript.Test/SequentialScript.Test.csproj @@ -71,6 +71,7 @@ + diff --git a/SequentialScript/Instructions.readme b/SequentialScript/Instructions.readme index aa44956..b4389e7 100644 --- a/SequentialScript/Instructions.readme +++ b/SequentialScript/Instructions.readme @@ -1,6 +1,6 @@ R e a d m e ----------- -V1.1-alpha.6 +V1.2-alpha See more information in the following link: https://github.com/space-engineers-hf/SequentialScript \ No newline at end of file diff --git a/SequentialScript/Instructions/InstructionParser.cs b/SequentialScript/Instructions/InstructionParser.cs index c0f00eb..144f772 100644 --- a/SequentialScript/Instructions/InstructionParser.cs +++ b/SequentialScript/Instructions/InstructionParser.cs @@ -125,40 +125,52 @@ private static InstructionCommand CreateCommand(System.Text.RegularExpressions.M .Where(line => !string.IsNullOrWhiteSpace(line) && !line.StartsWith("//")) // Ignore empty lines and comments. .Select((line, index) => { + Instruction instruction = null; + // Remove comments var lineComments = line.Split(new[] { "//" }, StringSplitOptions.None); - var lineWithoutComments = lineComments.First(); - - // Take BlockName and ActionName - var lineItems = lineWithoutComments.Trim().Split(new[] { "->" }, StringSplitOptions.RemoveEmptyEntries); - string blockName = null, actionName = null; - bool isValid; + var lineWithoutComments = lineComments.First().Trim(); - if (lineItems.Length > 0) + // Create instruction. + if (lineWithoutComments.Contains("->")) { - blockName = lineItems[0].Trim(); + // Instruction of IMyTerminalBlock -> Action + instruction = CreateInstruction(lineWithoutComments); } - if (lineItems.Length > 1) + else { - actionName = lineItems[1].Trim(); - } - isValid = lineItems.Length == 2; - - // Arguments - var arguments = GetArguments(actionName); + // Script reserved instructions. + var lineItems = lineWithoutComments.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + var command = lineItems.FirstOrDefault(); + if (command == null) + { + instruction = null; + } + else if (command.Equals("DELAY", StringComparison.OrdinalIgnoreCase)) + { + instruction = CreateInstructionDelay(lineItems); + } + if (instruction == null) + { + instruction = new Instruction + { + BlockName = null, + ActionName = null, + Arguments = null, + IsValid = false + }; + } + } return new { Line = index + 1, - BlockName = blockName, - ActionName = arguments[""].Trim(), - Arguments = arguments.Where(x => !string.IsNullOrEmpty(x.Key)).ToDictionary(x => x.Key, x => x.Value, StringComparer.OrdinalIgnoreCase), - IsValid = isValid + Instruction = instruction, }; }) ; - var invalid = actions.Where(x => !x.IsValid); + var invalid = actions.Where(x => !x.Instruction.IsValid); if (invalid.Any()) { throw new SyntaxException( @@ -197,13 +209,7 @@ private static InstructionCommand CreateCommand(System.Text.RegularExpressions.M { Alias = alias, PreviousAlias = previousActionsMatch.Select(x => x.Alias), - Instructions = actions.Select(x => new Instruction - { - BlockName = x.BlockName, - ActionName = x.ActionName, - Arguments = x.Arguments, - IsValid = x.IsValid - }) + Instructions = actions.Select(x => x.Instruction) }); } } @@ -237,6 +243,55 @@ private static InstructionCommand CreateCommand(System.Text.RegularExpressions.M return result; } + private static Instruction CreateInstruction(string lineWithoutComments) + { + // Take BlockName and ActionName + var lineItems = lineWithoutComments.Split(new[] { "->" }, StringSplitOptions.RemoveEmptyEntries); + string blockName = null, actionName = null; + bool isValid; + + if (lineItems.Length > 0) + { + blockName = lineItems[0].Trim(); + } + if (lineItems.Length > 1) + { + actionName = lineItems[1].Trim(); + } + isValid = lineItems.Length == 2; + + // Arguments + var arguments = GetArguments(actionName); + + // Return + return new Instruction + { + BlockName = blockName, + ActionName = arguments[""].Trim(), + Arguments = arguments.Where(x => !string.IsNullOrEmpty(x.Key)).ToDictionary(x => x.Key, x => x.Value, StringComparer.OrdinalIgnoreCase), + IsValid = isValid + }; + } + + private static Instruction CreateInstructionDelay(string[] lineItems) + { + string timeString = null; + + if (lineItems.Length >= 1) + { + timeString = lineItems[1]; + } + return new Instruction + { + BlockName = null, + ActionName = "DELAY", + Arguments = new Dictionary() { + { "TIME", timeString } + }, + IsValid = true + }; + } + private static ConditionCommandInstruction CreateConditionCommand(System.Text.RegularExpressions.Match commandMatch) { ConditionCommandInstruction result = null; diff --git a/SequentialScript/MDK/MDK.options.props b/SequentialScript/MDK/MDK.options.props index 2dab6fa..1d8bd19 100644 --- a/SequentialScript/MDK/MDK.options.props +++ b/SequentialScript/MDK/MDK.options.props @@ -6,10 +6,10 @@ --> 1.4.14 - no + yes - StripComments + Lite mdk diff --git a/SequentialScript/Program.cs b/SequentialScript/Program.cs index a0176d2..cb92548 100644 --- a/SequentialScript/Program.cs +++ b/SequentialScript/Program.cs @@ -30,6 +30,11 @@ partial class Program : MyGridProgram static readonly UpdateFrequency UPDATE_FREQUENCY = UpdateFrequency.Update10; // Update1, Update10, Update100 static readonly int UPDATE_TICKS = 0; // Very slow mode multiplier (for debug) + + /* ----------------------------------------------------------------------------------- */ + /* --- ¡¡¡IMPORTANT!!! Do not change anything below this line. --- */ + /* ----------------------------------------------------------------------------------- */ + #endregion DateTime _momento; @@ -76,7 +81,9 @@ public void Main(string argument, UpdateType updateSource) .OfType() .SelectMany(cmd => cmd.Body) .SelectMany(body => body.Instructions) - .Select(instruction => instruction.BlockName); + .Where(instruction => instruction.BlockName != null) + .Select(instruction => instruction.BlockName) + .Distinct(); AdvancedEcho($"Building dictionary", append: true); _blocksDictionary = Helper.CreateBlockDictionary(blockNames, _terminalBlocks, _terminalGroups); @@ -283,7 +290,7 @@ void EndCycle() void AdvancedEchoReset() { - _momento = DateTime.Now; + _momento = DateTime.UtcNow; } void AdvancedEcho(string message, bool append = false) @@ -297,7 +304,7 @@ void AdvancedEcho(string message, bool append = false) builder.Append(value); message = builder.ToString(); } - message = $"| Elapsed {(DateTime.Now - _momento).TotalMilliseconds:00}ms |\n{message}"; + message = $"| Elapsed {(DateTime.UtcNow - _momento).TotalMilliseconds:00}ms |\n{message}"; Echo(message); if (DEBUG_IN_SCREEN) diff --git a/SequentialScript/SequentialScript.csproj b/SequentialScript/SequentialScript.csproj index 3084c30..85ace97 100644 --- a/SequentialScript/SequentialScript.csproj +++ b/SequentialScript/SequentialScript.csproj @@ -99,8 +99,11 @@ + + + diff --git a/SequentialScript/Tasks/ITaskAction.cs b/SequentialScript/Tasks/ITaskAction.cs new file mode 100644 index 0000000..d68d997 --- /dev/null +++ b/SequentialScript/Tasks/ITaskAction.cs @@ -0,0 +1,52 @@ +using Sandbox.Game.EntityComponents; +using Sandbox.ModAPI.Ingame; +using Sandbox.ModAPI.Interfaces; +using SpaceEngineers.Game.ModAPI.Ingame; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Text; +using VRage; +using VRage.Collections; +using VRage.Game; +using VRage.Game.Components; +using VRage.Game.GUI.TextPanel; +using VRage.Game.ModAPI.Ingame; +using VRage.Game.ModAPI.Ingame.Utilities; +using VRage.Game.ObjectBuilders.Definitions; +using VRageMath; + +namespace IngameScript +{ + interface ITaskAction + { + + string ActionKey { get; } + + /// + /// Gets or sets if this task action must be checked during "" validation. + /// + bool IsCommandCondition { get; set; } + + /// + /// Gets or sets when this task started or null if it has not started yet. + /// + DateTime? StartTime { get; set; } + + /// + /// Starts the task. + /// + void Execute(); + + /// + /// Checks if this task has been ended. + /// + /// + /// + /// + bool Check(TaskStatusMode mode, DateTime? momento = null, StringBuilder debug = null); + + } +} diff --git a/SequentialScript/Tasks/Task.cs b/SequentialScript/Tasks/Task.cs index a91f679..7120f3d 100644 --- a/SequentialScript/Tasks/Task.cs +++ b/SequentialScript/Tasks/Task.cs @@ -25,7 +25,7 @@ class Task public string Alias { get; set; } public IEnumerable PreviousTasks { get; set; } - public IEnumerable Actions { get; set; } + public IEnumerable Actions { get; set; } public bool IsRunning { get; set; } public bool IsDone { get; set; } diff --git a/SequentialScript/Tasks/TaskAction.cs b/SequentialScript/Tasks/TaskAction.cs index 7b6cf9c..814f9de 100644 --- a/SequentialScript/Tasks/TaskAction.cs +++ b/SequentialScript/Tasks/TaskAction.cs @@ -6,6 +6,7 @@ using System.Collections; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics; using System.Linq; using System.Text; using VRage; @@ -20,20 +21,25 @@ namespace IngameScript { - class TaskAction + class TaskAction : ITaskAction { + public string ActionKey + { + get { return $"{this.Block.EntityId}\t{this.ActionProfile.GroupName}"; } + } + public IMyTerminalBlock Block { get; set; } public IActionProfile ActionProfile { get; set; } public IDictionary Arguments { get; set; } - + public DateTime? StartTime { get; set; } /// /// Gets or sets if this task action must be checked during "" validation. /// - public bool Check { get; set; } - + public bool IsCommandCondition { get; set; } + /// /// Gets or sets the number of milliseconds to wait before assume this task finished. /// can finish before if returns true. @@ -44,5 +50,62 @@ class TaskAction /// public int Wait { get; set; } + + public void Execute() + { + this.ActionProfile.OnActionCallback(this.Block, this.Arguments); + } + + public bool Check(TaskStatusMode mode, DateTime? momento = null, StringBuilder debug = null) + { + var action = this; + bool isCompleted = false; + bool printDebug = false; + string sufixDebug = null; + + switch (mode) + { + case TaskStatusMode.Condition: + // Condition wait is ignored. + if (action.Wait > -1) + { + sufixDebug = $"(wait:{action.Wait})"; + isCompleted = true; + printDebug = false; + } + break; + + case TaskStatusMode.Run: + if (action.Wait == 0) // No wait. + { + isCompleted = true; + } + else if (action.Wait > -1) // -1 wait until done + { + isCompleted = (action.StartTime != null && (((momento ?? DateTime.UtcNow) - action.StartTime.Value).TotalMilliseconds >= action.Wait)); // Wait for maximun... + } + sufixDebug = $"(wait:{action.Wait})"; + printDebug = true; + break; + + default: + break; + } + isCompleted |= action.ActionProfile.IsCompleteCallback(action.Block, action.Arguments); + if (printDebug && !isCompleted) + { + var isCompletedText = (isCompleted ? "Done" : "Pending"); + var actionDebug = action.ActionProfile.GetCompletionDetails(action.Block, action.Arguments); + + if (!string.IsNullOrWhiteSpace(actionDebug)) + { + sufixDebug += $" ({actionDebug})"; + } + debug?.AppendLine($" - {action.Block.DisplayNameText}.{action.ActionProfile.ActionNames.First()} ({isCompletedText})"); + debug?.AppendLine($" {sufixDebug?.Trim()} {action.StartTime?.ToString("HH:mm:ss")}"); + } + return isCompleted; + } + } } diff --git a/SequentialScript/Tasks/TaskActionDelay.cs b/SequentialScript/Tasks/TaskActionDelay.cs new file mode 100644 index 0000000..2056844 --- /dev/null +++ b/SequentialScript/Tasks/TaskActionDelay.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace IngameScript +{ + class TaskActionDelay : ITaskAction + { + + private string _name => $"delay_{DateTime.Now.Ticks}"; + private DateTime? _internalStartTime; + + /// + /// Time in miliseconds before this task is done. + /// + public int Delay { get; set; } + + public string ActionKey => _name; + public DateTime? StartTime { get; set; } + + public bool IsCommandCondition + { + get { return false; } + set { throw new NotImplementedException(); } + } + + public void Execute() + { + _internalStartTime = DateTime.UtcNow; + } + + public bool Check(TaskStatusMode mode, DateTime? momento = null, StringBuilder debug = null) + { + bool isCompleted; + string isCompletedText; + + if (_internalStartTime.HasValue) + { + var elapsedTimeSpan = (momento ?? DateTime.UtcNow) - _internalStartTime.Value; + var remainingTimeSpan = TimeSpan.FromMilliseconds(this.Delay) - elapsedTimeSpan; + + debug?.Append($"Delay: {remainingTimeSpan:hh\\:mm\\:ss}"); + isCompleted = (remainingTimeSpan <= TimeSpan.Zero); + } + else + { + debug?.Append($"Delay: not started"); + isCompleted = false; //not started yet. + } + isCompletedText = (isCompleted ? "Done" : "Pending"); + debug?.Append($" ({isCompletedText})"); + return isCompleted; + } + + } +} diff --git a/SequentialScript/Tasks/TaskEnums.cs b/SequentialScript/Tasks/TaskEnums.cs new file mode 100644 index 0000000..cede0d0 --- /dev/null +++ b/SequentialScript/Tasks/TaskEnums.cs @@ -0,0 +1,44 @@ +using Sandbox.Game.EntityComponents; +using Sandbox.Game.GameSystems; +using Sandbox.ModAPI.Ingame; +using Sandbox.ModAPI.Interfaces; +using SpaceEngineers.Game.ModAPI.Ingame; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Text; +using VRage; +using VRage.Collections; +using VRage.Game; +using VRage.Game.Components; +using VRage.Game.GUI.TextPanel; +using VRage.Game.ModAPI.Ingame; +using VRage.Game.ModAPI.Ingame.Utilities; +using VRage.Game.ObjectBuilders.Definitions; +using VRageMath; + +namespace IngameScript +{ + + enum TaskStatus + { + Pending, + Running, + Completed + } + + enum TaskStatusMode + { + /// + /// Checks if task is already done. + /// + Condition, + /// + /// Checks if every actions in the task have run and finished. + /// + Run + } + +} diff --git a/SequentialScript/Tasks/Tasks.cs b/SequentialScript/Tasks/Tasks.cs index 9590e9b..39c68cc 100644 --- a/SequentialScript/Tasks/Tasks.cs +++ b/SequentialScript/Tasks/Tasks.cs @@ -24,24 +24,6 @@ namespace IngameScript static class Tasks { - enum TaskStatus - { - Pending, - Running, - Completed - } - - enum TaskStatusMode - { - /// - /// Checks if task is already done. - /// - Condition, - /// - /// Checks if every actions in the task have run and finished. - /// - Run - } /// /// Creates a list of tasks from a list of . @@ -56,60 +38,26 @@ public static IEnumerable CreateTasks(IEnumerable instru foreach (var instructionBlock in instructions) { - var actions = new List(); + var actions = new List(); foreach (var instruction in instructionBlock.Instructions) { - IEnumerable blocks = null; - - // Try get blocks. - if (blockDictionary.TryGetValue(instruction.BlockName, out blocks)) + if (instruction.BlockName == null) { - foreach (var block in blocks) + // Script actions. + switch (instruction.ActionName) { - string argumentValue; - bool check; - int wait; - - // Check parameters - check = instruction.Arguments.TryGetValue("CHECK", out argumentValue) && (string.IsNullOrWhiteSpace(argumentValue) || argumentValue.Equals("true", StringComparison.OrdinalIgnoreCase)); - if (instruction.Arguments.TryGetValue("MAXWAIT", out argumentValue)) - { - if (string.IsNullOrWhiteSpace(argumentValue) || !int.TryParse(argumentValue, out wait)) - { - throw new FormatException($"'/MAXWAIT' must have a numeric value."); - } - } - else if (instruction.Arguments.TryGetValue("WAIT", out argumentValue)) - { - if (string.IsNullOrWhiteSpace(argumentValue) || !int.TryParse(argumentValue, out wait)) - { - throw new FormatException($"'/WAIT' must have a numeric value."); - } - } - else if (instruction.Arguments.TryGetValue("NOWAIT", out argumentValue) && (string.IsNullOrWhiteSpace(argumentValue) || argumentValue.Equals("true", StringComparison.OrdinalIgnoreCase))) - { - wait = 0; - } - else - { - wait = -1; - } - - // Add action. - actions.Add(new TaskAction - { - Block = block, - ActionProfile = ActionProfiles.GetActionProfile(block, instruction.ActionName), - Arguments = instruction.Arguments, - Check = check, - Wait = wait - }); + case "DELAY": + actions.Add(CreateTaskDelay(instruction)); + break; + default: + throw new Exception($"Action name: '{instruction.ActionName}'."); } } else { - throw new KeyNotFoundException($"No blocks found with name '{instruction.BlockName}'."); + // Block actions. + actions.AddRange(CreateTaskAction(instruction, blockDictionary)); } } result.Add(new Task @@ -127,6 +75,93 @@ public static IEnumerable CreateTasks(IEnumerable instru return result; } + private static IEnumerable CreateTaskAction(Instruction instruction, IDictionary> blockDictionary) + { + var list = new List(); + IEnumerable blocks = null; + + // Try get blocks. + if (blockDictionary.TryGetValue(instruction.BlockName, out blocks)) + { + string argumentValue; + bool check; + int wait; + + // Check parameters + check = instruction.Arguments.TryGetValue("CHECK", out argumentValue) && (string.IsNullOrWhiteSpace(argumentValue) || argumentValue.Equals("true", StringComparison.OrdinalIgnoreCase)); + if (instruction.Arguments.TryGetValue("MAXWAIT", out argumentValue)) + { + if (string.IsNullOrWhiteSpace(argumentValue) || !int.TryParse(argumentValue, out wait)) + { + throw new FormatException($"'/MAXWAIT' must have a numeric value."); + } + } + else if (instruction.Arguments.TryGetValue("WAIT", out argumentValue)) + { + if (string.IsNullOrWhiteSpace(argumentValue) || !int.TryParse(argumentValue, out wait)) + { + throw new FormatException($"'/WAIT' must have a numeric value."); + } + } + else if (instruction.Arguments.TryGetValue("NOWAIT", out argumentValue) && (string.IsNullOrWhiteSpace(argumentValue) || argumentValue.Equals("true", StringComparison.OrdinalIgnoreCase))) + { + wait = 0; + } + else + { + wait = -1; + } + + foreach (var block in blocks) + { + + // Add action. + list.Add(new TaskAction + { + Block = block, + ActionProfile = ActionProfiles.GetActionProfile(block, instruction.ActionName), + Arguments = instruction.Arguments, + IsCommandCondition = check, + Wait = wait + }); + } + } + else + { + throw new KeyNotFoundException($"No blocks found with name '{instruction.BlockName}'."); + } + return list; + } + + private static TaskActionDelay CreateTaskDelay(Instruction instruction) + { + string argumentValue; + int milliseconds; + + if (instruction.Arguments.TryGetValue("TIME", out argumentValue)) + { + if (string.IsNullOrWhiteSpace(argumentValue) || !int.TryParse(argumentValue, out milliseconds)) + { + throw new FormatException($"Delay time must be numeric."); + } + } + else + { + throw new FormatException($"No time defined for delay."); + } + + return new TaskActionDelay + { + Delay = milliseconds + }; + } + + /// + /// Starts task. + /// + /// + /// + /// public static IEnumerable Run(this IEnumerable tasks, StringBuilder debug = null) { var momento = DateTime.UtcNow; @@ -152,7 +187,7 @@ public static IEnumerable Run(this IEnumerable tasks, StringBuilder { foreach (var action in task.Actions) { - action.ActionProfile.OnActionCallback(action.Block, action.Arguments); + action.Execute(); action.StartTime = momento; } task.IsRunning = true; @@ -180,7 +215,7 @@ public static void Cancel(this IEnumerable tasks) public static bool IsCompleted(this IEnumerable tasks, StringBuilder debug = null) { bool result; - var checkActions = tasks.SelectMany(x => x.Actions).Where(x => x.Check); + var checkActions = tasks.SelectMany(x => x.Actions).Where(x => x.IsCommandCondition); debug?.AppendLine("Checking:"); if (!checkActions.Any()) @@ -239,67 +274,18 @@ private static bool IsDone(Task task, TaskStatusMode mode, DateTime? momento = n /// /// Checks if all have been completed. /// - private static bool IsDone(IEnumerable actions, TaskStatusMode mode, DateTime? momento = null, StringBuilder debug = null) + private static bool IsDone(IEnumerable actions, TaskStatusMode mode, DateTime? momento = null, StringBuilder debug = null) { - return actions.All(action => - { - bool isCompleted = false; - bool printDebug = false; - string sufixDebug = null; - - switch (mode) - { - case TaskStatusMode.Condition: - // Condition wait is ignored. - if (action.Wait > -1) - { - sufixDebug = $"(wait:{action.Wait})"; - isCompleted = true; - printDebug = false; - } - break; - - case TaskStatusMode.Run: - if (action.Wait == 0) // No wait. - { - isCompleted = true; - sufixDebug = $"(wait:{action.Wait})"; - } - else if (action.Wait > -1) // -1 wait until done - { - isCompleted = (action.StartTime != null && (((momento ?? DateTime.UtcNow) - action.StartTime.Value).TotalMilliseconds > action.Wait)); // Wait for maximun... - } - sufixDebug = $"(wait:{action.Wait})"; - printDebug = true; - break; - - default: - break; - } - isCompleted |= action.ActionProfile.IsCompleteCallback(action.Block, action.Arguments); - if (printDebug && !isCompleted) - { - var isCompletedText = (isCompleted ? "Done" : "Pending"); - var actionDebug = action.ActionProfile.GetCompletionDetails(action.Block, action.Arguments); - - if (!string.IsNullOrWhiteSpace(actionDebug)) - { - sufixDebug += $" ({actionDebug})"; - } - debug?.AppendLine($" - {action.Block.DisplayNameText}.{action.ActionProfile.ActionNames.First()} ({isCompletedText})"); - debug?.AppendLine($" {sufixDebug?.Trim()} {action.StartTime?.ToString("HH:mm:ss")}"); - } - return isCompleted; - }); + return actions.All(action => action.Check(mode, momento, debug)); } /// /// Returns the last action for each block taking in mind the running sequence. /// /// List of instruction blocks. - public static IEnumerable GetLastActions(this IEnumerable tasks) + public static IEnumerable GetLastActions(this IEnumerable tasks) { - var actionDictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); + var actionDictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); var validatedTasks = new List(); // All tasks already validated. IEnumerable validationTasks; // Tasks to validate for each iteration. var i = 0; @@ -312,7 +298,7 @@ public static IEnumerable GetLastActions(this IEnumerable task { foreach (var action in task.Actions) { - actionDictionary[$"{action.Block.EntityId}\t{action.ActionProfile.GroupName}"] = action; + actionDictionary[action.ActionKey] = action; } validatedTasks.Add(task); } diff --git a/docs/README.md b/docs/README.md index 16d3f8d..eb6eea9 100644 --- a/docs/README.md +++ b/docs/README.md @@ -5,6 +5,7 @@ - [Blocks](#blocks) - [Sequence](#sequence) - [Argumens](#arguments) + - [Delay](#delay) - [Comments](#comments) - [Action list](#action-list) - [Known issues](#known-issues) @@ -199,6 +200,22 @@ as @done #### Custom-defined arguments by action Some actions have their own arguments. Those arguments are defined in the [action list](#action-list). +### Delay +Include "DELAY" instruction (in milliseconds) when it is necessary to wait for a specific time before a sequence block is done. + +The following example waits for 3 seconds before switchs on the second light. +``` +[COMMAND_NAME] +run + Interior Light 1 -> Enable + delay 3000 // Waits for 3 seconds. +as @light1_on + +when @light1_on +run + Interior Light 2 -> Enable +as @light2_on +``` ### Comments