diff --git a/src/kOS.Safe.Test/Collections/CollectionValueTest.cs b/src/kOS.Safe.Test/Collections/CollectionValueTest.cs index 496d2796e9..ddb1e13a12 100644 --- a/src/kOS.Safe.Test/Collections/CollectionValueTest.cs +++ b/src/kOS.Safe.Test/Collections/CollectionValueTest.cs @@ -1,4 +1,4 @@ -using System; +using System; using kOS.Safe.Encapsulation; using kOS.Safe.Execution; using NUnit.Framework; @@ -18,7 +18,7 @@ public void Setup() cpu = new FakeCpu(); } - protected object InvokeDelegate(SerializableStructure stack, string suffixName, params object[] parameters) + protected object InvokeDelegate(kOS.Safe.Encapsulation.Structure stack, string suffixName, params object[] parameters) { var lengthObj = stack.GetSuffix(suffixName) as DelegateSuffixResult; Assert.IsNotNull(lengthObj); diff --git a/src/kOS.Safe.Test/Collections/LexiconTest.cs b/src/kOS.Safe.Test/Collections/LexiconTest.cs index 23463f3f57..bd5bb89bb9 100644 --- a/src/kOS.Safe.Test/Collections/LexiconTest.cs +++ b/src/kOS.Safe.Test/Collections/LexiconTest.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using kOS.Safe.Encapsulation; @@ -229,26 +229,6 @@ public void CopyIsDifferentObject() } - [Test] - public void CanFormatNumericKeys() - { - var map = MakeNestedExample(); - - var hasKeyInner = (BooleanValue)InvokeDelegate(map, "HASKEY" , new StringValue("inner")); - Assert.IsTrue(hasKeyInner); - - var inner = (Lexicon) ((Lexicon)map)[new StringValue("inner")]; - Assert.IsNotNull(inner); - - var hasNumericKey = (BooleanValue)InvokeDelegate(inner, "HASKEY" , new ScalarIntValue(3)); - Assert.IsTrue(hasNumericKey); - - var innerString = inner.ToString(); - - Assert.IsTrue(innerString.Contains("[\"2\"]")); - Assert.IsTrue(innerString.Contains("[3]")); - } - [Test] public void CanClearOnCaseChange() { diff --git a/src/kOS.Safe.Test/Collections/MixedCollectionPrintingTest.cs b/src/kOS.Safe.Test/Collections/MixedCollectionPrintingTest.cs index e090418fc3..8c235e501c 100644 --- a/src/kOS.Safe.Test/Collections/MixedCollectionPrintingTest.cs +++ b/src/kOS.Safe.Test/Collections/MixedCollectionPrintingTest.cs @@ -1,4 +1,4 @@ -using System; +using System; using kOS.Safe.Encapsulation; using kOS.Safe.Serialization; using NUnit.Framework; @@ -10,28 +10,188 @@ public class MixedCollectionPrintingTest : CollectionValueTest { [Test] - public void CanPrintListInLexicon() + public void CanSerialize() { - var list = new ListValue - { - new StringValue("First In List"), - new StringValue("Second In List"), - new StringValue("Last In List") - }; - var lexicon = new Lexicon - { - {new StringValue("list"), list}, - {new StringValue("not list"), new ScalarIntValue(2)} - }; + var runner = new KSRunner(@" +local l to lex( + ""Boolean"", true, + ""List"", list(1, false, ""foo""), + ""Pid"", PidLoop(1, 2, 3, 4, 5) +// ""Queue"", queue(1, false, ""foo""), +// ""Range"", range(1, 2), +// ""Double"", 1.1, +// ""Int"", 42, +// ""Stack"", stack(1, 2), +// ""String"", ""foo"", +// ""Set"", uniqueset(1, 2) +). +writejson(l, ""serialization.json""). +return l. +"); + Assert.AreEqual("", runner.Output); - var result = (StringValue)InvokeDelegate(lexicon, "DUMP"); + Lexicon l = runner.Result as Lexicon; + Assert.NotNull(l); + + var file = runner.Volume.RootHarddiskDirectory.GetFileContent("serialization.json"); + Assert.NotNull(file); + + string reference = @"{ + ""entries"": [ + { + ""value"": ""Boolean"", + ""$type"": ""kOS.Safe.Encapsulation.StringValue"" + }, + { + ""value"": true, + ""$type"": ""kOS.Safe.Encapsulation.BooleanValue"" + }, + { + ""value"": ""List"", + ""$type"": ""kOS.Safe.Encapsulation.StringValue"" + }, + { + ""items"": [ + { + ""value"": 1, + ""$type"": ""kOS.Safe.Encapsulation.ScalarIntValue"" + }, + { + ""value"": false, + ""$type"": ""kOS.Safe.Encapsulation.BooleanValue"" + }, + { + ""value"": ""foo"", + ""$type"": ""kOS.Safe.Encapsulation.StringValue"" + } + ], + ""$type"": ""kOS.Safe.Encapsulation.ListValue"" + }, + { + ""value"": ""Pid"", + ""$type"": ""kOS.Safe.Encapsulation.StringValue"" + }, + { + ""Kp"": 1, + ""Ki"": 2, + ""Kd"": 3, + ""Setpoint"": 0, + ""MaxOutput"": 5, + ""MinOutput"": 4, + ""ExtraUnwind"": false, + ""$type"": ""kOS.Safe.Encapsulation.PIDLoop"" + }, + { + ""value"": ""Queue"", + ""$type"": ""kOS.Safe.Encapsulation.StringValue"" + }, + { + ""items"": [ + { + ""value"": 1, + ""$type"": ""kOS.Safe.Encapsulation.ScalarIntValue"" + }, + { + ""value"": false, + ""$type"": ""kOS.Safe.Encapsulation.BooleanValue"" + }, + { + ""value"": ""foo"", + ""$type"": ""kOS.Safe.Encapsulation.StringValue"" + } + ], + ""$type"": ""kOS.Safe.Encapsulation.QueueValue"" + }, + { + ""value"": ""Range"", + ""$type"": ""kOS.Safe.Encapsulation.StringValue"" + }, + { + ""stop"": { + ""value"": 2, + ""$type"": ""kOS.Safe.Encapsulation.ScalarIntValue"" + }, + ""start"": { + ""value"": 1, + ""$type"": ""kOS.Safe.Encapsulation.ScalarIntValue"" + }, + ""step"": { + ""value"": 1, + ""$type"": ""kOS.Safe.Encapsulation.ScalarIntValue"" + }, + ""$type"": ""kOS.Safe.RangeValue"" + }, + { + ""value"": ""Double"", + ""$type"": ""kOS.Safe.Encapsulation.StringValue"" + }, + { + ""value"": 1.1, + ""$type"": ""kOS.Safe.Encapsulation.ScalarDoubleValue"" + }, + { + ""value"": ""Int"", + ""$type"": ""kOS.Safe.Encapsulation.StringValue"" + }, + { + ""value"": 42, + ""$type"": ""kOS.Safe.Encapsulation.ScalarIntValue"" + }, + { + ""value"": ""Stack"", + ""$type"": ""kOS.Safe.Encapsulation.StringValue"" + }, + { + ""items"": [ + { + ""value"": 2, + ""$type"": ""kOS.Safe.Encapsulation.ScalarIntValue"" + }, + { + ""value"": 1, + ""$type"": ""kOS.Safe.Encapsulation.ScalarIntValue"" + } + ], + ""$type"": ""kOS.Safe.Encapsulation.StackValue"" + }, + { + ""value"": ""String"", + ""$type"": ""kOS.Safe.Encapsulation.StringValue"" + }, + { + ""value"": ""foo"", + ""$type"": ""kOS.Safe.Encapsulation.StringValue"" + }, + { + ""value"": ""Set"", + ""$type"": ""kOS.Safe.Encapsulation.StringValue"" + }, + { + ""items"": [ + { + ""value"": 1, + ""$type"": ""kOS.Safe.Encapsulation.ScalarIntValue"" + }, + { + ""value"": 2, + ""$type"": ""kOS.Safe.Encapsulation.ScalarIntValue"" + } + ], + ""$type"": ""kOS.Safe.Encapsulation.UniqueSetValue"" + } + ], + ""$type"": ""kOS.Safe.Encapsulation.Lexicon"" +}".Replace("\r\n", "\n"); + + string serializationResult = file.String.Replace("\r\n", "\n"); + System.Diagnostics.Debug.WriteLine(serializationResult); + Assert.AreEqual(reference, serializationResult); - Assert.IsTrue(result.Contains("LEXICON of 2 items")); - Assert.IsTrue(result.Contains("[\"list\"] = LIST of 3 items")); - Assert.IsTrue(result.Contains("Last In List")); } + + [Test] public void DoesNotContainInvalidToString() { diff --git a/src/kOS.Safe.Test/Communication/MessageQueueTest.cs b/src/kOS.Safe.Test/Communication/MessageQueueTest.cs index 1a40b6681a..60903b5861 100644 --- a/src/kOS.Safe.Test/Communication/MessageQueueTest.cs +++ b/src/kOS.Safe.Test/Communication/MessageQueueTest.cs @@ -139,9 +139,9 @@ public void CanHandleSerializableStructures() { Lexicon lex = new Lexicon(); lex.Add(new StringValue("key1"), new StringValue("value1")); - queue.Push(new BaseMessage(new SafeSerializationMgr(null).Dump(lex), 0, 0)); + //queue.Push(new BaseMessage(new SafeSerializationMgr(null).Dump(lex), 0, 0)); - Lexicon read = new SafeSerializationMgr(null).CreateFromDump(queue.Pop().Content as Dump) as Lexicon; + Lexicon read = null;// new SafeSerializationMgr(null).CreateFromDump(queue.Pop().Content as Dump) as Lexicon; Assert.AreEqual(new StringValue("value1"), read[new StringValue("key1")]); } @@ -156,7 +156,7 @@ public void CanDump() GenericMessageQueue newQueue = new GenericMessageQueue(); - newQueue.LoadDump(queue.Dump()); + newQueue.LoadDump(queue.Dump(new DumperState()) as DumpList); newQueue.TimeProvider.SetTime(11); diff --git a/src/kOS.Safe.Test/Execution/Noop.cs b/src/kOS.Safe.Test/Execution/Noop.cs index c8ebc5df17..e2a7ddfc1e 100644 --- a/src/kOS.Safe.Test/Execution/Noop.cs +++ b/src/kOS.Safe.Test/Execution/Noop.cs @@ -1,4 +1,4 @@ -using kOS.Safe.Callback; +using kOS.Safe.Callback; using kOS.Safe.Execution; using kOS.Safe.Module; using kOS.Safe.Persistence; @@ -8,7 +8,7 @@ namespace kOS.Safe.Test.Execution { internal class NoopLogger : ILogger { - public void Log(Exception e) + public virtual void Log(Exception e) { Console.WriteLine(e); } @@ -17,12 +17,12 @@ public void Log(string text) { } - public void LogError(string s) + public virtual void LogError(string s) { Console.WriteLine(s); } - public void LogException(Exception exception) + public virtual void LogException(Exception exception) { Console.WriteLine(exception); } diff --git a/src/kOS.Safe.Test/Execution/Screen.cs b/src/kOS.Safe.Test/Execution/Screen.cs index e1b848dbde..e4ed94b1a5 100644 --- a/src/kOS.Safe.Test/Execution/Screen.cs +++ b/src/kOS.Safe.Test/Execution/Screen.cs @@ -1,6 +1,7 @@ -using kOS.Safe.Screen; +using kOS.Safe.Screen; using System; using System.Collections.Generic; +using System.Linq; namespace kOS.Safe.Test { @@ -8,6 +9,11 @@ internal class Screen : IScreenBuffer { private List output = new List(); + public override string ToString() + { + return string.Join(Environment.NewLine, output); + } + public void ClearOutput() { output.Clear(); diff --git a/src/kOS.Safe.Test/KSRunner.cs b/src/kOS.Safe.Test/KSRunner.cs new file mode 100644 index 0000000000..d54ec29c89 --- /dev/null +++ b/src/kOS.Safe.Test/KSRunner.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using kOS.Safe.Binding; +using kOS.Safe.Compilation; +using kOS.Safe.Compilation.KS; +using kOS.Safe.Encapsulation; +using kOS.Safe.Execution; +using kOS.Safe.Function; +using kOS.Safe.Persistence; +using kOS.Safe.Test.Execution; +using kOS.Safe.Utilities; + +namespace kOS.Safe.Test +{ + class KSRunner + { + [AssemblyWalk(AttributeType = typeof(BindingAttribute), InherritedType = typeof(SafeBindingBase), StaticRegisterMethod = "RegisterMethod")] + class BindingManager : BaseBindingManager + { + public BindingManager(SafeSharedObjects s) : base(s) { } + } + + public string KerboScript { get; private set; } + public Harddisk Volume { get; private set; } + + public KSRunner(string kerboscript) + { + KerboScript = kerboscript; + Run(); + } + + private string WrappedScript + { + get + { + return "print \"======DELIMITER======\".\n" + + "local _f to { " + KerboScript + " }.\n" + + "set TestResult to _f()."; + } + } + + public string Output + { + get + { + return string.Join("======DELIMITER======", _screen.ToString().Split(new string[] { "======DELIMITER======" }, StringSplitOptions.None).Skip(1)); + } + } + + public Encapsulation.Structure Result + { + get + { + return _result as Encapsulation.Structure; + } + } + + private void Run() + { + if (!_hasWalked) + { + if (SafeHouse.Logger == null) + SafeHouse.Logger = new NoopLogger(); + if (SafeHouse.Config == null) + SafeHouse.Init(new Config(), new VersionInfo(0, 0, 0, 0), "https://example.org", Environment.NewLine == "\r\n", "/dev/null"); + AssemblyWalkAttribute.Walk(); + _hasWalked = true; + } + + var shared = new SafeSharedObjects(); + shared.BindingMgr = new BindingManager(shared); + shared.FunctionManager = new FunctionManager(shared); + shared.GameEventDispatchManager = new NoopGameEventDispatchManager(); + shared.Processor = new NoopProcessor(); + shared.ScriptHandler = new KSScript(); + _screen = new Screen(); + shared.Screen = _screen; + shared.UpdateHandler = new UpdateHandler(); + shared.VolumeMgr = new VolumeManager(); + + shared.FunctionManager.Load(); + + Volume = new Harddisk(640*1024); // 640K ought to be enough for anybody + shared.VolumeMgr.Add(Volume); + shared.VolumeMgr.SwitchTo(Volume); + + var cpu = new CPU(shared); + shared.Cpu = cpu; + cpu.ShouldSwallowExceptions = false; + + shared.Cpu.Boot(); + + bool finished = false; + shared.BindingMgr.AddSetter("TESTRESULT", (s) => { + finished = true; + _result = s; + }); + + GlobalPath path = shared.VolumeMgr.GlobalPathFromObject("0:/test"); + var compiled = shared.ScriptHandler.Compile(path, 1, WrappedScript, "test", new CompilerOptions() + { + LoadProgramsInSameAddressSpace = false, + IsCalledFromRun = false, + FuncManager = shared.FunctionManager + }); + shared.Cpu.GetCurrentContext().AddParts(compiled); + + for(int i = 0; i < 100; i++) + { + shared.UpdateHandler.UpdateFixedObservers(0.01); + + if (finished) + return; + } + + throw new TimeoutException("Execution did not finish in time.\n\nScreen output:" + Output); + } + + private static bool _hasWalked = false; + private object _result = null; + private Screen _screen = null; + } +} diff --git a/src/kOS.Safe.Test/Serialization/FormatterTest.cs b/src/kOS.Safe.Test/Serialization/FormatterTest.cs index 5e33b81374..f1aab818e9 100644 --- a/src/kOS.Safe.Test/Serialization/FormatterTest.cs +++ b/src/kOS.Safe.Test/Serialization/FormatterTest.cs @@ -1,4 +1,4 @@ -using System; +using System; using kOS.Safe.Encapsulation; using NUnit.Framework; using kOS.Safe.Serialization; @@ -7,9 +7,6 @@ namespace kOS.Safe.Test.Serialization { public abstract class FormatterTest { - protected abstract IFormatReader FormatReader { get; } - protected abstract IFormatWriter FormatWriter { get; } - [SetUp] public void Setup() { @@ -98,12 +95,12 @@ public void CanSerializeQueues() private string Serialize(IDumper o) { - return new SafeSerializationMgr(null).Serialize(o, FormatWriter); + return "";// return new SafeSerializationMgr(null).Serialize(o, FormatWriter); } private IDumper Deserialize(string s) { - return new SafeSerializationMgr(null).Deserialize(s, FormatReader); + return null;// return new SafeSerializationMgr(null).Deserialize(s, FormatReader); } } } diff --git a/src/kOS.Safe.Test/Serialization/JSONFormatterTest.cs b/src/kOS.Safe.Test/Serialization/JSONFormatterTest.cs deleted file mode 100644 index c1b2c5b50a..0000000000 --- a/src/kOS.Safe.Test/Serialization/JSONFormatterTest.cs +++ /dev/null @@ -1,27 +0,0 @@ -using kOS.Safe.Encapsulation; -using kOS.Safe.Serialization; -using NUnit.Framework; -using kOS.Safe.Utilities; - -namespace kOS.Safe.Test.Serialization -{ - [TestFixture] - public class JSONFormatterTest : FormatterTest - { - protected override IFormatReader FormatReader - { - get - { - return JsonFormatter.ReaderInstance; - } - } - - protected override IFormatWriter FormatWriter - { - get - { - return JsonFormatter.WriterInstance; - } - } - } -} diff --git a/src/kOS.Safe.Test/Serialization/TerminalFormatterTest.cs b/src/kOS.Safe.Test/Serialization/TerminalFormatterTest.cs deleted file mode 100644 index b4de8ca949..0000000000 --- a/src/kOS.Safe.Test/Serialization/TerminalFormatterTest.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using kOS.Safe.Encapsulation; -using NUnit.Framework; -using kOS.Safe.Serialization; - -namespace kOS.Safe.Test.Serialization -{ - [TestFixture] - public class TerminalFormatterTest - { - [SetUp] - public void Setup() - { - - } - - [Test] - public void CanSerializeLexicons() - { - var lex = new Lexicon(); - var nested = new Lexicon(); - - lex[new StringValue("key1")] = new StringValue("value1"); - lex[new StringValue("2")] = new ScalarIntValue(10); - lex[new ScalarIntValue(2)] = new ScalarIntValue(11); // make sure int 2 is different than string "2" - lex[new StringValue("key3")] = nested; - - nested[new StringValue("nested1")] = new StringValue("nested1value"); - nested[new StringValue("nested2")] = new StringValue("nested2value"); - - var lines = new string[] { "LEXICON of 4 items:", "[\"key1\"] = \"value1\"", "[\"2\"] = 10", "[2] = 11", "[\"key3\"] = LEXICON of 2 items:", - " [\"nested1\"] = \"nested1value\"", " [\"nested2\"] = \"nested2value\""}; - - Assert.AreEqual(string.Join(Environment.NewLine, lines), Serialize(lex)); - } - - private string Serialize(SerializableStructure o) - { - return new SafeSerializationMgr(null).Serialize(o, TerminalFormatter.Instance, false); - } - } -} diff --git a/src/kOS.Safe.Test/Structure/NoArgsSuffixTest.cs b/src/kOS.Safe.Test/Structure/NoArgsSuffixTest.cs index c9682fec1a..276b194401 100644 --- a/src/kOS.Safe.Test/Structure/NoArgsSuffixTest.cs +++ b/src/kOS.Safe.Test/Structure/NoArgsSuffixTest.cs @@ -1,12 +1,14 @@ -using System; +using System; using kOS.Safe.Encapsulation; using kOS.Safe.Encapsulation.Suffixes; using NUnit.Framework; using kOS.Safe.Test.Opcode; using kOS.Safe.Execution; +using kOS.Safe.Utilities; namespace kOS.Safe.Test.Structure { + [KOSNomenclature("MockStructure")] public class MockStructure : Encapsulation.Structure { diff --git a/src/kOS.Safe.Test/Structure/StructureTest.cs b/src/kOS.Safe.Test/Structure/StructureTest.cs index f805b3f080..8718bcb964 100644 --- a/src/kOS.Safe.Test/Structure/StructureTest.cs +++ b/src/kOS.Safe.Test/Structure/StructureTest.cs @@ -1,4 +1,4 @@ -using System; +using System; using kOS.Safe.Encapsulation; using kOS.Safe.Encapsulation.Suffixes; using kOS.Safe.Exceptions; @@ -11,6 +11,8 @@ namespace kOS.Safe.Test.Structure [TestFixture] public class StructureTest { + [KOSNomenclature("StructureTest_TestStructure")] + public class TestStructure: Encapsulation.Structure { public static void TestAddGlobal(string name, ISuffix suffix) diff --git a/src/kOS.Safe.Test/kOS.Safe.Test.csproj b/src/kOS.Safe.Test/kOS.Safe.Test.csproj index 8d68b2a28e..e627e7802d 100644 --- a/src/kOS.Safe.Test/kOS.Safe.Test.csproj +++ b/src/kOS.Safe.Test/kOS.Safe.Test.csproj @@ -1,5 +1,6 @@  + Debug AnyCPU @@ -11,6 +12,8 @@ v4.5 512 + + true @@ -49,6 +52,8 @@ + + @@ -67,11 +72,8 @@ - - - @@ -118,4 +120,10 @@ + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + \ No newline at end of file diff --git a/src/kOS.Safe.Test/packages.config b/src/kOS.Safe.Test/packages.config index 55f87c5fc4..7c0651a0a7 100644 --- a/src/kOS.Safe.Test/packages.config +++ b/src/kOS.Safe.Test/packages.config @@ -2,4 +2,5 @@ + \ No newline at end of file diff --git a/src/kOS.Safe/Binding/BaseBindingManager.cs b/src/kOS.Safe/Binding/BaseBindingManager.cs new file mode 100644 index 0000000000..5cd013852d --- /dev/null +++ b/src/kOS.Safe/Binding/BaseBindingManager.cs @@ -0,0 +1,156 @@ +using kOS.Safe.Binding; +using kOS.Safe.Utilities; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace kOS.Safe.Binding +{ + public class BaseBindingManager : IBindingManager + { + private readonly SafeSharedObjects shared; + private readonly List bindings = new List(); + private readonly Dictionary variables; + + // Note: When we were using .Net 3.5, This used to be a Dictionary rather than a HashSet of pairs. But that had to + // change because of one .Net 4.x change to how reflection on Attributes works. In .Net 3.5, an Attribute called + // [Foo(1,2)] attached to classA was considered un-equal to an attribute with the same values ([Foo(1,2)]) attached + // to classB. But in .Net 4.0, which class the attribute is attached to is no longer part of its equality test, + // therefore both those examples would be "equal" classes because they are the same name Foo with the same paramters (1,2). + // This meant that when we had many classes decorated with exactly the same thing, [Binding("ksp")], these Attributes + // could be unique keys in a Dictionary in .Net 3.5 because they weren't attached to the same class, but in .Net 4.0 + // they became key clashes because they were now considered "equal" and all such Attributes after the first were + // refusing to be stored in the dictionary. + private static readonly HashSet> rawAttributes = new HashSet>(); + + public BaseBindingManager(SafeSharedObjects shared) + { + variables = new Dictionary(StringComparer.OrdinalIgnoreCase); + this.shared = shared; + this.shared.BindingMgr = this; + } + + public void Load() + { + var contexts = new string[1]; + contexts[0] = "ksp"; + + bindings.Clear(); + variables.Clear(); + + foreach (KeyValuePair attrTypePair in rawAttributes) + { + var type = attrTypePair.Value; + if (attrTypePair.Key.Contexts.Any() && !attrTypePair.Key.Contexts.Intersect(contexts).Any()) continue; + var instanceWithABinding = (SafeBindingBase)Activator.CreateInstance(type); + instanceWithABinding.AddTo(shared); + bindings.Add(instanceWithABinding); + + LoadInstanceWithABinding(instanceWithABinding); + } + } + + protected virtual void LoadInstanceWithABinding(SafeBindingBase instanceWithABinding) + { + + } + + public static void RegisterMethod(BindingAttribute attr, Type type) + { + KeyValuePair attrTypePair = new KeyValuePair(attr, type); + if (attr != null && !rawAttributes.Contains(attrTypePair)) + { + rawAttributes.Add(attrTypePair); + } + } + + public void AddBoundVariable(string name, BindingGetDlg getDelegate, BindingSetDlg setDelegate) + { + BoundVariable variable; + if (variables.ContainsKey(name)) + { + variable = variables[name]; + } + else + { + variable = new BoundVariable + { + Name = name, + }; + variables.Add(name, variable); + shared.Cpu.AddVariable(variable, name, false); + } + + if (getDelegate != null) + variable.Get = getDelegate; + + if (setDelegate != null) + variable.Set = setDelegate; + } + + public void AddGetter(string name, BindingGetDlg dlg) + { + AddBoundVariable(name, dlg, null); + } + + public void AddGetter(IEnumerable names, BindingGetDlg dlg) + { + foreach (var name in names) + { + AddBoundVariable(name, dlg, null); + } + } + + public void AddSetter(string name, BindingSetDlg dlg) + { + AddBoundVariable(name, null, dlg); + } + + public void AddSetter(IEnumerable names, BindingSetDlg dlg) + { + foreach (var name in names) + { + AddBoundVariable(name, null, dlg); + } + } + + public bool HasGetter(string name) + { + return variables.ContainsKey(name); + } + + /// + /// Indicates that the binding should not be cached during execution + /// + /// The binding to modify + public void MarkVolatile(string name) + { + variables[name].Volatile = true; + } + + public void PreUpdate() + { + foreach (var variable in variables) + { + variable.Value.ClearCache(); + } + // update the bindings + foreach (var b in bindings) + { + b.Update(); + } + } + + public void PostUpdate() + { + } + + public virtual void ToggleFlyByWire(string paramName, bool enabled) + { + } + + public virtual void SelectAutopilotMode(string autopilotMode) + { + } + } +} \ No newline at end of file diff --git a/src/kOS.Safe/Communication/BaseMessage.cs b/src/kOS.Safe/Communication/BaseMessage.cs index e71cd9e333..79b17b29f8 100644 --- a/src/kOS.Safe/Communication/BaseMessage.cs +++ b/src/kOS.Safe/Communication/BaseMessage.cs @@ -15,81 +15,51 @@ public class BaseMessage : IDumper, IComparable public double SentAt { get; set; } public double ReceivedAt { get; set; } - /// - /// Message content can be either a simple encapsulated type (something that implements ISerializableValue) or a Dump. - /// - /// Currently kOS serialization doesn't allow primitives as top level objects, but Messages do allow them. - /// - private object content; + public Structure Content { get; set; } - public object Content - { - get - { - return content; - } - set - { - if (value is PrimitiveStructure || value is Dump) - { - content = value; - } else - { - throw new KOSCommunicationException("Message can only contain primitives and serializable types"); - } - } - } - - // Only used by CreateFromDump() and derived classes. - // Don't make it public because it leaves fields - // unpopulated: - protected BaseMessage() - { - - } - - public BaseMessage(Dump content, double sentAt, double receivedAt) - { - Content = content; - SentAt = sentAt; - ReceivedAt = receivedAt; - } - - public BaseMessage(PrimitiveStructure content, double sentAt, double receivedAt) + public BaseMessage(Structure content, double sentAt, double receivedAt) { Content = content; SentAt = sentAt; ReceivedAt = receivedAt; } - // Required for all IDumpers for them to work, but can't enforced by the interface because it's static: - public static BaseMessage CreateFromDump(SafeSharedObjects shared, Dump d) + public virtual Dump Dump(DumperState s) { - var newObj = new BaseMessage(); - newObj.LoadDump(d); - return newObj; - } - - public virtual Dump Dump() - { - DumpWithHeader dump = new DumpWithHeader(); - - dump.Header = "MESSAGE"; + var dump = new DumpDictionary(typeof(BaseMessage)); - dump.Add(DumpSentAt, SentAt); - dump.Add(DumpReceivedAt, ReceivedAt); - dump.Add(DumpContent, content); + using (var context = s.Context(this)) + { + dump.Add(DumpSentAt, SentAt); + dump.Add(DumpReceivedAt, ReceivedAt); + dump.Add(DumpContent, Content, context); + } return dump; } - public virtual void LoadDump(Dump dump) + [DumpDeserializer] + public static BaseMessage CreateFromDump(DumpDictionary d, SafeSharedObjects shared) { - SentAt = Convert.ToDouble(dump[DumpSentAt]); - ReceivedAt = Convert.ToDouble(dump[DumpReceivedAt]); - content = dump[DumpContent]; + return new BaseMessage(d.GetStructure(DumpContent, shared), d.GetDouble(DumpSentAt), d.GetDouble(DumpReceivedAt)); } + [DumpPrinter] + public static void Print(DumpDictionary d, IndentedStringBuilder sb) + { + sb.Append("Message [sent: "); + sb.Append(d.GetDouble(DumpSentAt).ToString()); + sb.Append(", received: "); + sb.Append(d.GetDouble(DumpReceivedAt).ToString()); + sb.Append("]:"); + + using (sb.Indent()) + { + var inner = d.GetDump(DumpContent); + inner.WriteReadable(sb); + } + } + public int CompareTo(BaseMessage other) { return ReceivedAt.CompareTo(other.ReceivedAt); diff --git a/src/kOS.Safe/Communication/GenericMessageQueue.cs b/src/kOS.Safe/Communication/GenericMessageQueue.cs index b803fa62e0..0d7e9bda40 100644 --- a/src/kOS.Safe/Communication/GenericMessageQueue.cs +++ b/src/kOS.Safe/Communication/GenericMessageQueue.cs @@ -58,14 +58,6 @@ public GenericMessageQueue() this.timeProvider = Activator.CreateInstance(typeof(TP)) as CurrentTimeProvider; } - // Required for all IDumpers for them to work, but can't enforced by the interface because it's static: - public static GenericMessageQueue CreateFromDump(SafeSharedObjects shared, Dump d) - { - var newObj = new GenericMessageQueue(); - newObj.LoadDump(d); - return newObj; - } - private void RemoveMessage(KeyValuePair> queueItem, M message) { queueItem.Value.Remove(message); @@ -137,30 +129,13 @@ public override string ToString() return "MESSAGE QUEUE"; } - public Dump Dump() - { - DumpWithHeader dump = new DumpWithHeader(); - dump.Header = "MESSAGE QUEUE"; - - int i = 0; - - foreach (M message in Messages) - { - dump.Add(i, message); - - i++; - } - - return dump; - } - - public void LoadDump(Dump dump) + public void LoadDump(DumpList dump) { queue.Clear(); - foreach (KeyValuePair entry in dump) + for(int i = 0; i < dump.Count; i++) { - M message = entry.Value as M; + M message = dump[i] as M; if (message != null) { @@ -169,5 +144,17 @@ public void LoadDump(Dump dump) } } + public Dump Dump(DumperState s) + { + var dump = new DumpList(this.GetType()); + + foreach (M message in Messages) + { + using (var c = s.Context(this)) + dump.Add(message, c); + } + + return dump; + } } } diff --git a/src/kOS.Safe/Encapsulation/BooleanValue.cs b/src/kOS.Safe/Encapsulation/BooleanValue.cs index 35365df669..e2b339433b 100644 --- a/src/kOS.Safe/Encapsulation/BooleanValue.cs +++ b/src/kOS.Safe/Encapsulation/BooleanValue.cs @@ -2,21 +2,14 @@ using kOS.Safe.Serialization; using System; using System.Reflection; +using System.Globalization; namespace kOS.Safe.Encapsulation { [kOS.Safe.Utilities.KOSNomenclature("Boolean")] public class BooleanValue : PrimitiveStructure, IConvertible { - // internalValue is *almost* immutable. - // It is supposed to be immutable (readonly keyword here) except that - // it can't be and also fit the design pattern kOS uses for Serializable structures. - // That pattern is to load from a dump by creating an instance with a dummy - // constructor first, then populate it with LoadDump(). To populate it with LoadDump(), - // the internal representation cannot be readonly. Populating from a dump should be the - // ONLY place the immutability rule is violated. - private bool internalValue; - + private readonly bool internalValue; public bool Value { get { return internalValue; } } public BooleanValue(bool value) @@ -41,11 +34,6 @@ public override object ToPrimitive() return Value; } - public override string ToString() - { - return Value.ToString(); - } - public override bool Equals(object obj) { if (obj == null) return false; @@ -252,24 +240,25 @@ ulong IConvertible.ToUInt64(IFormatProvider provider) throw new KOSCastException(typeof(BooleanValue), typeof(ulong)); } - // Required for all IDumpers for them to work, but can't enforced by the interface because it's static: - public static BooleanValue CreateFromDump(SafeSharedObjects shared, Dump d) - { - var newObj = new BooleanValue(); - newObj.LoadDump(d); - return newObj; - } - public override Dump Dump() + public override Dump Dump(DumperState s) { - DumpWithHeader dump = new DumpWithHeader(); - + DumpDictionary dump = new DumpDictionary(typeof(BooleanValue)); + dump.Add("value", internalValue); return dump; } - public override void LoadDump(Dump dump) + + [DumpDeserializer] + public static BooleanValue CreateFromDump(DumpDictionary d, SafeSharedObjects shared) + { + return new BooleanValue(d.GetBool("value")); + } + + [DumpPrinter] + public static void Print(DumpDictionary d, IndentedStringBuilder sb) { - internalValue = Convert.ToBoolean(dump["value"]); + sb.Append(d.GetBool("value").ToString(CultureInfo.InvariantCulture)); } } } \ No newline at end of file diff --git a/src/kOS.Safe/Encapsulation/BuiltinDelegate.cs b/src/kOS.Safe/Encapsulation/BuiltinDelegate.cs index 4cdd7dede6..7d3fed983b 100644 --- a/src/kOS.Safe/Encapsulation/BuiltinDelegate.cs +++ b/src/kOS.Safe/Encapsulation/BuiltinDelegate.cs @@ -1,4 +1,4 @@ -using kOS.Safe.Compilation; +using kOS.Safe.Compilation; using kOS.Safe.Execution; namespace kOS.Safe.Encapsulation @@ -33,11 +33,13 @@ public override bool IsDead() return false; // builtins cannot be dead. } + /* public override string ToString() { return string.Format("BuiltinDelegate(cpu={0}, Name={1},\n {2})", Cpu, Name, base.ToString()); } + */ public override void PushUnderArgs() { diff --git a/src/kOS.Safe/Encapsulation/ConstantValue.cs b/src/kOS.Safe/Encapsulation/ConstantValue.cs index da762e691a..11aeb86319 100644 --- a/src/kOS.Safe/Encapsulation/ConstantValue.cs +++ b/src/kOS.Safe/Encapsulation/ConstantValue.cs @@ -93,11 +93,6 @@ static ConstantValue() AddGlobalSuffix("BOLTZMANN", new StaticSuffix(() => BoltzmannConst)); AddGlobalSuffix("IDEALGAS", new StaticSuffix(() => IdealGasConst)); } - - public override string ToString() - { - return string.Format("{0} Constants", base.ToString()); - } } } \ No newline at end of file diff --git a/src/kOS.Safe/Encapsulation/EnumerableValue.cs b/src/kOS.Safe/Encapsulation/EnumerableValue.cs index 23a31a207d..e5f8e0bc07 100644 --- a/src/kOS.Safe/Encapsulation/EnumerableValue.cs +++ b/src/kOS.Safe/Encapsulation/EnumerableValue.cs @@ -7,7 +7,7 @@ namespace kOS.Safe.Encapsulation { [kOS.Safe.Utilities.KOSNomenclature("Enumerable")] - public abstract class EnumerableValue : SerializableStructure, IEnumerable + public abstract class EnumerableValue : Structure, IEnumerable where TE : IEnumerable where T : Structure { @@ -37,21 +37,14 @@ public bool Contains(T item) return InnerEnumerable.Contains(item); } - public override string ToString() + public void PopulateDumpList(DumpList d, DumperState s) { - return new SafeSerializationMgr(null).ToString(this); - } - - public override Dump Dump() - { - var result = new DumpWithHeader - { - Header = label + " of " + InnerEnumerable.Count() + " items:" - }; - - result.Add(kOS.Safe.Dump.Items, InnerEnumerable.Cast().ToList()); - - return result; + using (var context = s.Context(this)) { + foreach (var i in InnerEnumerable) + { + d.Add(i, context); + } + } } private void InitializeEnumerableSuffixes() diff --git a/src/kOS.Safe/Encapsulation/Enumerator.cs b/src/kOS.Safe/Encapsulation/Enumerator.cs index 074f3cf12c..6d0993fcc2 100644 --- a/src/kOS.Safe/Encapsulation/Enumerator.cs +++ b/src/kOS.Safe/Encapsulation/Enumerator.cs @@ -29,10 +29,5 @@ private void EnumeratorInitializeSuffixes() AddSuffix("INDEX", new NoArgsSuffix(() => index)); AddSuffix("VALUE", new NoArgsSuffix(() => FromPrimitiveWithAssert(enumerator.Current))); } - - public override string ToString() - { - return string.Format("{0} Iterator", base.ToString()); - } } } \ No newline at end of file diff --git a/src/kOS.Safe/Encapsulation/KOSDelegate.cs b/src/kOS.Safe/Encapsulation/KOSDelegate.cs index 8c3dab822b..6d905787e2 100644 --- a/src/kOS.Safe/Encapsulation/KOSDelegate.cs +++ b/src/kOS.Safe/Encapsulation/KOSDelegate.cs @@ -1,4 +1,4 @@ -using kOS.Safe.Encapsulation.Suffixes; +using kOS.Safe.Encapsulation.Suffixes; using kOS.Safe.Exceptions; using kOS.Safe.Execution; using System.Collections.Generic; @@ -169,6 +169,7 @@ public KOSDelegate Bind(params Structure[] args) return preBoundDel; } + /* public override string ToString() { StringBuilder str = new StringBuilder(); @@ -179,6 +180,6 @@ public override string ToString() } str.Append(")"); return str.ToString(); - } + }*/ } } diff --git a/src/kOS.Safe/Encapsulation/Lexicon.cs b/src/kOS.Safe/Encapsulation/Lexicon.cs index e621281051..594f3f741f 100644 --- a/src/kOS.Safe/Encapsulation/Lexicon.cs +++ b/src/kOS.Safe/Encapsulation/Lexicon.cs @@ -12,7 +12,7 @@ namespace kOS.Safe.Encapsulation { [kOS.Safe.Utilities.KOSNomenclature("Lexicon")] [kOS.Safe.Utilities.KOSNomenclature("Lex", CSharpToKOS = false) ] - public class Lexicon : SerializableStructure, IDictionary, IIndexable + public class Lexicon : Structure, IDictionary, IIndexable { [Function("lex", "lexicon")] public class FunctionLexicon : SafeFunctionBase @@ -88,14 +88,6 @@ public Lexicon(IEnumerable> lexicon) } } - // Required for all IDumpers for them to work, but can't enforced by the interface because it's static: - public static Lexicon CreateFromDump(SafeSharedObjects shared, Dump d) - { - var newObj = new Lexicon(); - newObj.LoadDump(d); - return newObj; - } - private void FillWithEnumerableValues(IEnumerable values) { if ((values.Count() == 1) && (values.First() is IEnumerable)) { @@ -297,10 +289,6 @@ public void SetIndex(int index, Structure value) internalDictionary[FromPrimitiveWithAssert(index)] = value; } - public override string ToString() - { - return new SafeSerializationMgr(null).ToString(this); - } // Try to call the normal SetSuffix that all structures do, but if that fails, // then try to use this suffix name as a key and set the value in the lexicon @@ -394,35 +382,53 @@ public override ListValue GetSuffixNames() } return new ListValue(theList.OrderBy(item => item.ToString())); } - public override Dump Dump() - { - var result = new DumpWithHeader - { - Header = "LEXICON of " + internalDictionary.Count() + " items:" - }; - List list = new List(); + public override Dump Dump(DumperState s) + { + var dump = new DumpLexicon(typeof(Lexicon)); - foreach (KeyValuePair entry in internalDictionary) + using (var context = s.Context(this)) { - list.Add(entry.Key); - list.Add(entry.Value); + foreach (KeyValuePair entry in internalDictionary) + { + dump.Add(entry.Key, entry.Value, context); + } } - result.Add(kOS.Safe.Dump.Entries, list); - - return result; + return dump; } - public override void LoadDump(Dump dump) + [DumpDeserializer] + public static Lexicon CreateFromDump(DumpLexicon d, SafeSharedObjects shared) { - internalDictionary.Clear(); + return new Lexicon(d.GetStructures(shared)); + } - List values = (List)dump[kOS.Safe.Dump.Entries]; + [DumpPrinter] + public static void Print(DumpLexicon d, IndentedStringBuilder sb) + { + sb.Append("LEXICON of "); + sb.Append(d.Count.ToString()); + sb.Append(" items"); + if (d.Count > 0) + sb.Append(":"); - for (int i = 0; 2 * i < values.Count; i++) + var kvpairs = d.GetItems(); + foreach (var kv in kvpairs) { - internalDictionary.Add(Structure.FromPrimitiveWithAssert(values[2 * i]), Structure.FromPrimitiveWithAssert(values[2 * i + 1])); + sb.AppendLine(); + var keyBuilder = new SingleLineIndentedStringBuilder(); + kv.Key.WriteReadable(keyBuilder); + sb.Append(keyBuilder.ToString()); + if (!keyBuilder.IsSingleLine) + { + sb.Append("..."); + } + sb.Append(": "); + using (sb.Indent()) + { + kv.Value.WriteReadable(sb); + } } } } diff --git a/src/kOS.Safe/Encapsulation/ListValue.cs b/src/kOS.Safe/Encapsulation/ListValue.cs index 5650380282..79d08e2ece 100644 --- a/src/kOS.Safe/Encapsulation/ListValue.cs +++ b/src/kOS.Safe/Encapsulation/ListValue.cs @@ -6,6 +6,7 @@ using kOS.Safe.Properties; using kOS.Safe.Serialization; using kOS.Safe.Function; +using System.Globalization; namespace kOS.Safe.Encapsulation { @@ -23,14 +24,6 @@ public ListValue() RegisterInitializer(ListInitializeSuffixes); } - // Required for all IDumpers for them to work, but can't enforced by the interface because it's static: - public static ListValue CreateFromDump(SafeSharedObjects shared, Dump d) - { - var newObj = new ListValue(); - newObj.LoadDump(d); - return newObj; - } - public int Count => Collection.Count; public void CopyTo(T[] array, int arrayIndex) => Collection.CopyTo(array, arrayIndex); @@ -61,18 +54,6 @@ public T this[int index] } } - public override void LoadDump(Dump dump) - { - Collection.Clear(); - - List values = (List)dump[kOS.Safe.Dump.Items]; - - foreach (object item in values) - { - Collection.Add((T)FromPrimitive(item)); - } - } - private void ListInitializeSuffixes() { AddSuffix("COPY", new NoArgsSuffix> (() => new ListValue(this))); @@ -145,6 +126,53 @@ public void Insert(int index, T item) CheckReadOnly(); Collection.Insert(index, item); } + + public override Dump Dump(DumperState s) + { + var dump = new DumpList(typeof(ListValue)); + PopulateDumpList(dump, s); + return dump; + } + + [DumpDeserializer] + public static ListValue CreateFromDump(DumpList d, SafeSharedObjects shared) + { + var result = new ListValue(); + for (int i = 0; i < d.Count; i++) + { + T value = d.GetDeserialized(i, shared) as T; + if (value == null) + throw new KOSSerializationException("Serialized object contains an object with the wrong type."); + result.Add(value); + } + return result; + } + + [DumpPrinter] + public static void Print(DumpList d, IndentedStringBuilder sb) + { + sb.Append("LIST of "); + sb.Append(d.Count.ToString()); + sb.Append(" items"); + if (d.Count > 0) + sb.Append(":"); + + int maxwidth = (d.Count.ToString(CultureInfo.InvariantCulture) + ". ").Length; + + for (int i = 0; i < d.Count; i++) + { + sb.AppendLine(); + var keyBuilder = new SingleLineIndentedStringBuilder(); + + string key = (i.ToString(CultureInfo.InvariantCulture) + ". ").PadRight(maxwidth); + sb.Append(key); + + using (sb.Indent()) + { + d[i].WriteReadable(sb); + } + } + } } [kOS.Safe.Utilities.KOSNomenclature("List", KOSToCSharp = false)] // one-way because the generic templated ListValue is the canonical one. @@ -174,14 +202,6 @@ public ListValue(IEnumerable toCopy) : base(toCopy) RegisterInitializer(InitializeSuffixes); } - // Required for all IDumpers for them to work, but can't enforced by the interface because it's static: - public static new ListValue CreateFromDump(SafeSharedObjects shared, Dump d) - { - var newObj = new ListValue(); - newObj.LoadDump(d); - return newObj; - } - private void InitializeSuffixes() => AddSuffix("COPY", new NoArgsSuffix(() => new ListValue(this))); diff --git a/src/kOS.Safe/Encapsulation/NoDelegate.cs b/src/kOS.Safe/Encapsulation/NoDelegate.cs index 585532cd7f..3b5544a6b8 100644 --- a/src/kOS.Safe/Encapsulation/NoDelegate.cs +++ b/src/kOS.Safe/Encapsulation/NoDelegate.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using kOS.Safe.Execution; using kOS.Safe.Exceptions; @@ -42,11 +42,6 @@ public override Structure CallWithArgsPushedAlready() { throw new KOSCannotCallException(); } - - public override string ToString() - { - return string.Format("DoNothingDelegate"); - } public override bool Equals(object o) { diff --git a/src/kOS.Safe/Encapsulation/PIDLoop.cs b/src/kOS.Safe/Encapsulation/PIDLoop.cs index c2c36af9c9..0214eb56b9 100644 --- a/src/kOS.Safe/Encapsulation/PIDLoop.cs +++ b/src/kOS.Safe/Encapsulation/PIDLoop.cs @@ -3,11 +3,12 @@ using kOS.Safe.Function; using kOS.Safe.Exceptions; using kOS.Safe.Serialization; +using System.Globalization; namespace kOS.Safe.Encapsulation { [kOS.Safe.Utilities.KOSNomenclature("PIDLoop")] - public class PIDLoop : SerializableStructure + public class PIDLoop : Structure { [Function("pidloop")] public class PIDLoopConstructor : SafeFunctionBase @@ -267,12 +268,6 @@ public void ResetI() LastSampleTime = double.MaxValue; } - public override string ToString() - { - return string.Format("PIDLoop(Kp:{0}, Ki:{1}, Kd:{2}, Min:{3}, Max:{4}, Setpoint:{5}, Error:{6}, Output:{7})", - Kp, Ki, Kd, MinOutput, MaxOutput, Setpoint, Error, Output); - } - public string ToCSVString() { return string.Format("{0},{1},{2},{3},{4},{5},{6},{7},{8}", @@ -284,37 +279,41 @@ public string ConstrutorString() return string.Format("pidloop({0}, {1}, {2}, {3}, {4})", Ki, Kp, Kd, MaxOutput, ExtraUnwind); } - // Required for all IDumpers for them to work, but can't enforced by the interface because it's static: - public static PIDLoop CreateFromDump(SafeSharedObjects shared, Dump d) + public override Dump Dump(DumperState s) { - var newObj = new PIDLoop(); - newObj.LoadDump(d); - return newObj; + DumpDictionary dump = new DumpDictionary(typeof(ScalarDoubleValue)); + + dump.Add("Kp", Kp); + dump.Add("Ki", Ki); + dump.Add("Kd", Kd); + dump.Add("Setpoint", Setpoint); + dump.Add("MinOutput", MinOutput); + dump.Add("MaxOutput", MaxOutput); + dump.Add("ExtraUnwind", ExtraUnwind); + + return dump; } - public override Dump Dump() + [DumpDeserializer] + public static PIDLoop CreateFromDump(DumpDictionary d, SafeSharedObjects shared) { - var result = new DumpWithHeader { Header = "PIDLoop" }; - result.Add("Kp", Kp); - result.Add("Ki", Ki); - result.Add("Kd", Kd); - result.Add("Setpoint", Setpoint); - result.Add("MaxOutput", MaxOutput); - result.Add("MinOutput", MinOutput); - result.Add("ExtraUnwind", ExtraUnwind); - + double Kp = d.GetDouble("Kp"); + double Ki = d.GetDouble("Ki"); + double Kd = d.GetDouble("Kd"); + double Setpoint = d.GetDouble("Setpoint"); + double MinOutput = d.GetDouble("MinOutput"); + double MaxOutput = d.GetDouble("MaxOutput"); + bool ExtraUnwind = d.GetBool("ExtraUnwind"); + + var result = new PIDLoop(Kp, Ki, Kd, MaxOutput, MinOutput, extraUnwind: ExtraUnwind); + result.Setpoint = Setpoint; return result; - } + } - public override void LoadDump(Dump dump) + [DumpPrinter] + public static void Print(DumpDictionary d, IndentedStringBuilder sb) { - Kp = Convert.ToDouble(dump["Kp"]); - Ki = Convert.ToDouble(dump["Ki"]); - Kd = Convert.ToDouble(dump["Kd"]); - Setpoint = Convert.ToDouble(dump["Setpoint"]); - MinOutput = Convert.ToDouble(dump["MinOutput"]); - MaxOutput = Convert.ToDouble(dump["MaxOutput"]); - ExtraUnwind = Convert.ToBoolean(dump["ExtraUnwind"]); + sb.Append(d.GetDouble("value").ToString(CultureInfo.InvariantCulture)); } } } \ No newline at end of file diff --git a/src/kOS.Safe/Encapsulation/PrimitiveStructure.cs b/src/kOS.Safe/Encapsulation/PrimitiveStructure.cs index 80ff44b343..fcc7f6289f 100644 --- a/src/kOS.Safe/Encapsulation/PrimitiveStructure.cs +++ b/src/kOS.Safe/Encapsulation/PrimitiveStructure.cs @@ -4,7 +4,7 @@ namespace kOS.Safe.Encapsulation { [kOS.Safe.Utilities.KOSNomenclature("Structure", KOSToCSharp = false)] - public abstract class PrimitiveStructure : SerializableStructure + public abstract class PrimitiveStructure : Structure { public abstract object ToPrimitive(); } diff --git a/src/kOS.Safe/Encapsulation/QueueValue.cs b/src/kOS.Safe/Encapsulation/QueueValue.cs index 9c91ac7e3e..08544b9828 100644 --- a/src/kOS.Safe/Encapsulation/QueueValue.cs +++ b/src/kOS.Safe/Encapsulation/QueueValue.cs @@ -37,8 +37,18 @@ public void Push(T val) InnerEnumerable.Enqueue(val); } - public override void LoadDump(Dump dump) + public Dump Dump() { + /* + Dump result = base.Dump(); + result.Annotations.Add(0, "<-- front"); + return result; + */ + return null; + } + public void LoadDump(Dump dump) + { + /* InnerEnumerable.Clear(); List values = (List)dump[kOS.Safe.Dump.Items]; @@ -46,7 +56,7 @@ public override void LoadDump(Dump dump) foreach (object item in values) { InnerEnumerable.Enqueue((T)FromPrimitive(item)); - } + }*/ } private void QueueInitializeSuffixes() diff --git a/src/kOS.Safe/Encapsulation/RangeValue.cs b/src/kOS.Safe/Encapsulation/RangeValue.cs index 79a621a4fc..11d6f779c6 100644 --- a/src/kOS.Safe/Encapsulation/RangeValue.cs +++ b/src/kOS.Safe/Encapsulation/RangeValue.cs @@ -61,15 +61,17 @@ private void InitializeRangeSuffixes() AddSuffix("STEP", new NoArgsSuffix(() => InnerEnumerable.Step)); } - public override void LoadDump(Dump dump) + public void LoadDump(Dump dump) { + /* InnerEnumerable.Stop = Convert.ToDouble(dump[DumpStop]); InnerEnumerable.Start = Convert.ToDouble(dump[DumpStart]); - InnerEnumerable.Step = Convert.ToDouble(dump[DumpStep]); + InnerEnumerable.Step = Convert.ToDouble(dump[DumpStep]);*/ } - public override Dump Dump() + public Dump Dump() { + /* DumpWithHeader result = new DumpWithHeader(); result.Header = "RANGE"; @@ -78,12 +80,8 @@ public override Dump Dump() result.Add(DumpStart, InnerEnumerable.Start); result.Add(DumpStep, InnerEnumerable.Step); - return result; - } - - public override string ToString() - { - return "RANGE(" + InnerEnumerable.Start + ", " + InnerEnumerable.Stop + ", " + InnerEnumerable.Step + ")"; + return result;*/ + return null; } } diff --git a/src/kOS.Safe/Encapsulation/ScalarDoubleValue.cs b/src/kOS.Safe/Encapsulation/ScalarDoubleValue.cs index c620bcc792..22f1614c0c 100644 --- a/src/kOS.Safe/Encapsulation/ScalarDoubleValue.cs +++ b/src/kOS.Safe/Encapsulation/ScalarDoubleValue.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using kOS.Safe.Serialization; namespace kOS.Safe.Encapsulation @@ -8,6 +9,8 @@ public class ScalarDoubleValue : ScalarValue { public static ScalarDoubleValue Zero = new ScalarDoubleValue(0); + public double Value { get; private set; } + public override bool IsDouble { get { return true; } @@ -18,6 +21,9 @@ public override bool IsInt get { return false; } } + public override int GetIntValue() { return (int)Value; } + public override double GetDoubleValue() { return Value; } + public override bool BooleanMeaning { get { return (double)Value != 0d; } @@ -47,24 +53,30 @@ public static ScalarDoubleValue MaxValue() return new ScalarDoubleValue(double.MaxValue); } - // Required for all IDumpers for them to work, but can't enforced by the interface because it's static: - public static ScalarDoubleValue CreateFromDump(SafeSharedObjects shared, Dump d) - { - var newObj = new ScalarDoubleValue(); - newObj.LoadDump(d); - return newObj; - } - public override Dump Dump() + public override Dump Dump(DumperState s) { - DumpWithHeader dump = new DumpWithHeader(); + DumpDictionary dump = new DumpDictionary(typeof(ScalarDoubleValue)); dump.Add("value", Value); return dump; } - public override void LoadDump(Dump dump) + + [DumpDeserializer] + public static ScalarDoubleValue CreateFromDump(DumpDictionary d, SafeSharedObjects shared) + { + return new ScalarDoubleValue(d.GetDouble("value")); + } + + [DumpPrinter] + public static void Print(DumpDictionary d, IndentedStringBuilder sb) + { + sb.Append(d.GetDouble("value").ToString(CultureInfo.InvariantCulture)); + } + + public override object ToPrimitive() { - Value = Convert.ToDouble(dump["value"]); + return Value; } } } \ No newline at end of file diff --git a/src/kOS.Safe/Encapsulation/ScalarIntValue.cs b/src/kOS.Safe/Encapsulation/ScalarIntValue.cs index 7355bd61f7..9e20144376 100644 --- a/src/kOS.Safe/Encapsulation/ScalarIntValue.cs +++ b/src/kOS.Safe/Encapsulation/ScalarIntValue.cs @@ -1,5 +1,6 @@ using kOS.Safe.Serialization; using System; +using System.Globalization; namespace kOS.Safe.Encapsulation { @@ -11,6 +12,8 @@ public class ScalarIntValue : ScalarValue public static ScalarIntValue One = new ScalarIntValue(1); public static ScalarIntValue Two = new ScalarIntValue(2); + public int Value { get; private set; } + public override bool IsDouble { get { return false; } @@ -21,16 +24,12 @@ public override bool IsInt get { return true; } } - public override bool BooleanMeaning - { - get { return (int)Value != 0; } - } + public override int GetIntValue() { return Value; } + public override double GetDoubleValue() { return Value; } - // All serializable structures need a default constructor, but it can - // be private to prevent it from being used externally: - private ScalarIntValue() + public override bool BooleanMeaning { - + get { return Value != 0; } } public ScalarIntValue(int value) @@ -53,24 +52,30 @@ public static ScalarIntValue MaxValue() return new ScalarIntValue(int.MaxValue); } - // Required for all IDumpers for them to work, but can't enforced by the interface because it's static: - public static ScalarIntValue CreateFromDump(SafeSharedObjects shared, Dump d) - { - var newObj = new ScalarIntValue(); - newObj.LoadDump(d); - return newObj; - } - public override Dump Dump() + public override Dump Dump(DumperState s) { - DumpWithHeader dump = new DumpWithHeader(); + DumpDictionary dump = new DumpDictionary(typeof(ScalarIntValue)); dump.Add("value", Value); return dump; } - public override void LoadDump(Dump dump) + + [DumpDeserializer] + public static ScalarIntValue CreateFromDump(DumpDictionary d, SafeSharedObjects shared) + { + return new ScalarIntValue((int)d.GetDouble("value")); + } + + [DumpPrinter] + public static void Print(DumpDictionary d, IndentedStringBuilder sb) + { + sb.Append(((int)d.GetDouble("value")).ToString(CultureInfo.InvariantCulture)); + } + + public override object ToPrimitive() { - Value = Convert.ToInt32(dump["value"]); + return Value; } } } \ No newline at end of file diff --git a/src/kOS.Safe/Encapsulation/ScalarValue.cs b/src/kOS.Safe/Encapsulation/ScalarValue.cs index 8b1855faf9..01bc709abf 100644 --- a/src/kOS.Safe/Encapsulation/ScalarValue.cs +++ b/src/kOS.Safe/Encapsulation/ScalarValue.cs @@ -37,17 +37,11 @@ public bool IsValid } } - public object Value { get; protected set; } - protected ScalarValue() { InitializeSuffixes(); } - public override object ToPrimitive () - { - return Value; - } public void InitializeSuffixes() { @@ -136,22 +130,17 @@ public static bool TryParseDouble(string str, out ScalarValue result) return false; } - public int GetIntValue() - { - return Convert.ToInt32(Value); - } - - public double GetDoubleValue() - { - return Convert.ToDouble(Value); - } + public abstract int GetIntValue(); + public abstract double GetDoubleValue(); + /* public override string ToString() { if (IsInt) return GetIntValue().ToString(); else if (IsDouble) return GetDoubleValue().ToString(); return "NaN"; } + */ public override bool Equals(object obj) { @@ -174,7 +163,7 @@ public override bool Equals(object obj) if (converter != null) { var val = (ScalarValue)converter.Invoke(null, new[] { obj }); - if (Value == val.Value) return true; + if (ToPrimitive() == val.ToPrimitive()) return true; } } return false; @@ -182,7 +171,7 @@ public override bool Equals(object obj) public override int GetHashCode() { - return Value.GetHashCode(); + return ToPrimitive().GetHashCode(); } public static ScalarValue Add(ScalarValue val1, ScalarValue val2) @@ -271,7 +260,7 @@ public static ScalarValue Abs(ScalarValue val) public static ScalarValue operator +(ScalarValue val) { - return Create(val.Value); + return Create(val.ToPrimitive()); } public static ScalarValue operator -(ScalarValue val) diff --git a/src/kOS.Safe/Encapsulation/StackValue.cs b/src/kOS.Safe/Encapsulation/StackValue.cs index 1eed16dae6..53a1fce5de 100644 --- a/src/kOS.Safe/Encapsulation/StackValue.cs +++ b/src/kOS.Safe/Encapsulation/StackValue.cs @@ -42,8 +42,17 @@ public void Push(T val) InnerEnumerable.Push(val); } - public override void LoadDump(Dump dump) + public Dump Dump() { + /* + Dump result = base.Dump(); + result.Annotations.Add(0, "<-- top"); + return result;*/ + return null; + } + public void LoadDump(Dump dump) + { + /* InnerEnumerable.Clear(); List values = ((List)dump[kOS.Safe.Dump.Items]); @@ -53,7 +62,7 @@ public override void LoadDump(Dump dump) foreach (object item in values) { InnerEnumerable.Push((T)Structure.FromPrimitive(item)); - } + }*/ } private void StackInitializeSuffixes() diff --git a/src/kOS.Safe/Encapsulation/StringValue.cs b/src/kOS.Safe/Encapsulation/StringValue.cs index 24f6056eff..9d5971d732 100644 --- a/src/kOS.Safe/Encapsulation/StringValue.cs +++ b/src/kOS.Safe/Encapsulation/StringValue.cs @@ -20,14 +20,7 @@ namespace kOS.Safe.Encapsulation [KOSNomenclature("String")] public class StringValue : PrimitiveStructure, IIndexable, IConvertible, IEnumerable { - // internalString is *almost* immutable. - // It is supposed to be immutable (readonly keyword here) except that - // it can't be and also fit the design pattern kOS uses for Serializable structures. - // That pattern is to load from a dump by creating an instance with a dummy - // constructor first, then populate it with LoadDump(). To populate it with LoadDump(), - // the internal representation cannot be readonly. Populating from a dump should be - // the only place the immutability rule is violated. - private string internalString; + private readonly string internalString; public static StringValue Empty { get; } = new StringValue(); public static StringValue None { get; } = new StringValue("None"); @@ -45,7 +38,7 @@ public StringValue(string stringValue) public StringValue(StringValue stringValue) { - internalString = stringValue.ToString(); + internalString = stringValue.internalString; RegisterInitializer(StringInitializeSuffixes); } @@ -57,7 +50,7 @@ public StringValue(char ch) public override object ToPrimitive() { - return ToString(); + return internalString; } public ScalarValue Length @@ -342,34 +335,36 @@ public static implicit operator StringValue(string value) public static StringValue operator +(StringValue val1, StringValue val2) { - return new StringValue(val1.ToString() + val2.ToString()); + return new StringValue(val1.internalString + val2.internalString); } public static StringValue operator +(StringValue val1, Structure val2) { - return new StringValue(val1.ToString() + val2.ToString()); + return new StringValue(val1.internalString + val2.ToString()); } public static StringValue operator +(Structure val1, StringValue val2) { - return new StringValue(val1.ToString() + val2.ToString()); - } - - public override string ToString() - { - return this; + return new StringValue(val1.ToString() + val2.internalString); } public override bool Equals(object obj) { if (obj == null) return false; - if (obj is StringValue || obj is string) + if (obj is string) + return string.Equals(internalString, (string)obj, StringComparison.OrdinalIgnoreCase); + if (obj is StringValue) { - return string.Equals(internalString, obj.ToString(), StringComparison.OrdinalIgnoreCase); + return string.Equals(internalString, ((StringValue)obj).internalString, StringComparison.OrdinalIgnoreCase); } return false; } + public override string ToString() + { + return internalString; + } + public override int GetHashCode() { return internalString.GetHashCode(); @@ -467,24 +462,30 @@ ulong IConvertible.ToUInt64(IFormatProvider provider) throw new KOSCastException(typeof(StringValue), typeof(UInt64)); } - // Required for all IDumpers for them to work, but can't enforced by the interface because it's static: - public static StringValue CreateFromDump(SafeSharedObjects shared, Dump d) - { - var newObj = new StringValue(); - newObj.LoadDump(d); - return newObj; - } - public override Dump Dump() + + #region Serialization + public override Dump Dump(DumperState s) { - DumpWithHeader dump = new DumpWithHeader(); + DumpDictionary dump = new DumpDictionary(this.GetType()); dump.Add("value", internalString); return dump; } - public override void LoadDump(Dump dump) + + [DumpDeserializer] + public static StringValue CreateFromDump(DumpDictionary d, SafeSharedObjects shared) + { + return new StringValue(d.GetString("value")); + } + + [DumpPrinter] + public static void Print(DumpDictionary dump, IndentedStringBuilder sb) { - internalString = Convert.ToString(dump["value"]); + sb.Append("\""); + sb.Append(dump.GetString("value").Replace("\"", "\"\"")); + sb.Append("\""); } + #endregion } } diff --git a/src/kOS.Safe/Encapsulation/Structure.cs b/src/kOS.Safe/Encapsulation/Structure.cs index 3027491585..17eaddf808 100644 --- a/src/kOS.Safe/Encapsulation/Structure.cs +++ b/src/kOS.Safe/Encapsulation/Structure.cs @@ -11,7 +11,7 @@ namespace kOS.Safe.Encapsulation { [KOSNomenclature("Structure")] - public abstract class Structure : ISuffixed + public abstract class Structure : ISuffixed, IDumper { private static readonly IDictionary> globalSuffixes; private readonly IDictionary instanceSuffixes; @@ -61,15 +61,33 @@ private void InitializeInstanceSuffixes() // enabling this one: // AddSuffix("TYPENAME", new NoArgsSuffix(() => GetType().ToString())); - AddSuffix("TOSTRING", new NoArgsSuffix(() => ToString())); + AddSuffix("TOSTRING", new NoArgsSuffix(() => ToIngameString())); AddSuffix("HASSUFFIX", new OneArgsSuffix(HasSuffix)); AddSuffix("SUFFIXNAMES", new NoArgsSuffix>(GetSuffixNames)); - AddSuffix("ISSERIALIZABLE", new NoArgsSuffix(() => this is SerializableStructure)); + AddSuffix("ISSERIALIZABLE", new NoArgsSuffix(() => IsSerializable)); AddSuffix("TYPENAME", new NoArgsSuffix(() => new StringValue(KOSName))); AddSuffix("ISTYPE", new OneArgsSuffix(GetKOSIsType)); AddSuffix("INHERITANCE", new NoArgsSuffix(GetKOSInheritance)); } + public bool IsSerializable + { + get + { + DumperState s = new DumperState(); + Dump d = Dump(s); + return d.IsSerializable; + } + } + private string ToIngameString() + { + DumperState s = new DumperState(); + Dump d = Dump(s); + IndentedStringBuilder sb = new IndentedStringBuilder(); + d.WriteReadable(sb); + return sb.ToString(); + } + protected void AddSuffix(string suffixName, ISuffix suffixToAdd) { AddSuffix(new[]{suffixName}, suffixToAdd); @@ -278,7 +296,7 @@ public virtual StringValue GetKOSInheritance() public override string ToString() { - return KOSNomenclature.GetKOSName(GetType()) + ": \"\""; // print as the KOSNomenclature string name, will look like: Structure: "" + return ToIngameString(); } /// @@ -358,5 +376,10 @@ public static object ToPrimitive(object value) return value; } + + public virtual Dump Dump(DumperState s) + { + return new DumpOpaque(KOSNomenclature.GetKOSName(GetType())); + } } } diff --git a/src/kOS.Safe/Encapsulation/TerminalStruct.cs b/src/kOS.Safe/Encapsulation/TerminalStruct.cs index eaf775ea2f..4f7cb01ce2 100644 --- a/src/kOS.Safe/Encapsulation/TerminalStruct.cs +++ b/src/kOS.Safe/Encapsulation/TerminalStruct.cs @@ -174,10 +174,5 @@ public TerminalInput GetTerminalInputInstance() terminalInput = new TerminalInput(shared); return terminalInput; } - - public override string ToString() - { - return string.Format("{0} Terminal", base.ToString()); - } } } diff --git a/src/kOS.Safe/Encapsulation/UniqueSetValue.cs b/src/kOS.Safe/Encapsulation/UniqueSetValue.cs index 4e0e1c0da9..7110ab0d9c 100644 --- a/src/kOS.Safe/Encapsulation/UniqueSetValue.cs +++ b/src/kOS.Safe/Encapsulation/UniqueSetValue.cs @@ -45,13 +45,9 @@ public bool Remove(T item) return Collection.Remove(item); } - public void Clear() - { - Collection.Clear(); - } - - public override void LoadDump(Dump dump) + public void LoadDump(Dump dump) { + /* Collection.Clear(); List values = (List)dump[kOS.Safe.Dump.Items]; @@ -59,7 +55,7 @@ public override void LoadDump(Dump dump) foreach (object item in values) { Collection.Add((T)FromPrimitive(item)); - } + }*/ } private void SetInitializeSuffixes() diff --git a/src/kOS.Safe/Encapsulation/UserDelegate.cs b/src/kOS.Safe/Encapsulation/UserDelegate.cs index 0f2e0946dd..9f11b90ef2 100644 --- a/src/kOS.Safe/Encapsulation/UserDelegate.cs +++ b/src/kOS.Safe/Encapsulation/UserDelegate.cs @@ -1,4 +1,4 @@ -using kOS.Safe.Encapsulation.Suffixes; +using kOS.Safe.Encapsulation.Suffixes; using kOS.Safe.Exceptions; using kOS.Safe.Execution; using kOS.Safe.Compilation; @@ -167,11 +167,13 @@ private void CaptureClosure() Closure = new List(); // make sure it exists as an empty list so we don't have to have 'if null' checks everwywhere. } + /* public override string ToString() { return string.Format("UserDelegate(cpu={0}, entryPoint={1}, Closure={2},\n {3})", (Cpu == null ? "(No CPU)" : Cpu.ToString()), EntryPoint, Closure, base.ToString()); } + */ public override bool Equals(object obj) { diff --git a/src/kOS.Safe/Encapsulation/VersionInfo.cs b/src/kOS.Safe/Encapsulation/VersionInfo.cs index aecd7b0a3f..e0c34dbe6b 100644 --- a/src/kOS.Safe/Encapsulation/VersionInfo.cs +++ b/src/kOS.Safe/Encapsulation/VersionInfo.cs @@ -27,9 +27,11 @@ private void VersionInitializeSuffixes() AddSuffix("BUILD", new StaticSuffix(() => build)); } + /* public override string ToString() { return string.Format("{0}.{1}.{2}.{3}", major, minor, patch, build); } + */ } } diff --git a/src/kOS.Safe/Execution/CPU.cs b/src/kOS.Safe/Execution/CPU.cs index b7fec478f9..3403d25cd2 100644 --- a/src/kOS.Safe/Execution/CPU.cs +++ b/src/kOS.Safe/Execution/CPU.cs @@ -95,6 +95,7 @@ public int NextTriggerInstanceId } public double SessionTime { get { return currentTime; } } + public bool ShouldSwallowExceptions { get; set; } public List ProfileResult { get; private set; } @@ -115,6 +116,7 @@ public int NextTriggerInstanceId public CPU(SafeSharedObjects shared) { + ShouldSwallowExceptions = true; this.shared = shared; this.shared.Cpu = this; stack = new Stack(); @@ -1417,6 +1419,8 @@ public void KOSFixedUpdate(double deltaTime) PopFirstContext(); stack.Clear(); // If breaking all execution, get rid of the cruft here too. } + if (!ShouldSwallowExceptions) + throw; } updateWatch.Stop(); diff --git a/src/kOS.Safe/Function/Misc.cs b/src/kOS.Safe/Function/Misc.cs index ccaae4cbe1..6440af9553 100644 --- a/src/kOS.Safe/Function/Misc.cs +++ b/src/kOS.Safe/Function/Misc.cs @@ -15,7 +15,25 @@ public class FunctionPrint : SafeFunctionBase { public override void Execute(SafeSharedObjects shared) { - string textToPrint = PopValueAssert(shared).ToString(); + object objectToPrint = PopValueAssert(shared); + + string textToPrint = null; + if (objectToPrint is StringValue) + { + textToPrint = ((StringValue)objectToPrint).ToPrimitive().ToString(); + } + else if (objectToPrint is Structure) + { + var dump = ((Structure)objectToPrint).Dump(new Serialization.DumperState()); + var builder = new IndentedStringBuilder(); + dump.WriteReadable(builder); + textToPrint = builder.ToString(); + } + else + { + textToPrint = objectToPrint.ToString(); + } + AssertArgBottomAndConsume(shared); shared.Screen.Print(textToPrint); } diff --git a/src/kOS.Safe/Function/Persistence.cs b/src/kOS.Safe/Function/Persistence.cs index 59a248e969..7da2626d5d 100644 --- a/src/kOS.Safe/Function/Persistence.cs +++ b/src/kOS.Safe/Function/Persistence.cs @@ -251,15 +251,19 @@ public class FunctionWriteJson : SafeFunctionBase public override void Execute(SafeSharedObjects shared) { object pathObject = PopValueAssert(shared, true); - SerializableStructure serialized = PopValueAssert(shared, true) as SerializableStructure; + Structure obj = PopValueAssert(shared, true) as Structure; AssertArgBottomAndConsume(shared); - if (serialized == null) - { - throw new KOSException("This type is not serializable"); - } + if (obj == null) + throw new KOSException("Unable to serialize this object. Can only serialize Structures."); + + DumperState s = new DumperState(); + var dump = obj.Dump(s); + if (!dump.IsSerializable) + throw new KOSException("Unable to serialize this structure, it contains unserializable components. Please check :ISSERIALIZABLE before calling WRITEJSON."); - string serializedString = new SafeSerializationMgr(shared).Serialize(serialized, JsonFormatter.WriterInstance); + var formatter = new JsonFormatter(); + string serializedString = formatter.Write(dump); FileContent fileContent = new FileContent(serializedString); @@ -288,8 +292,8 @@ public override void Execute(SafeSharedObjects shared) throw new KOSException("File does not exist: " + path); } - Structure read = new SafeSerializationMgr(shared).Deserialize(volumeFile.ReadAll().String, JsonFormatter.ReaderInstance) as SerializableStructure; - ReturnValue = read; + //Structure read = new SafeSerializationMgr(shared).Deserialize(volumeFile.ReadAll().String, JsonFormatter.ReaderInstance) as Structure; + ReturnValue = new Lexicon(); } } diff --git a/src/kOS.Safe/Persistence/FileContent.cs b/src/kOS.Safe/Persistence/FileContent.cs index 7cff1169fd..d5ceb697fe 100644 --- a/src/kOS.Safe/Persistence/FileContent.cs +++ b/src/kOS.Safe/Persistence/FileContent.cs @@ -13,7 +13,7 @@ namespace kOS.Safe.Persistence { [kOS.Safe.Utilities.KOSNomenclature("FileContent")] - public class FileContent : SerializableStructure, IEnumerable + public class FileContent : Structure, IEnumerable { private static readonly Encoding fileEncoding = Encoding.UTF8; private const string DumpContent = "content"; @@ -70,13 +70,15 @@ private ListValue ContentAsIntList() return new ListValue(Bytes.Select(x => new ScalarIntValue(x))); } - public override Dump Dump() + public Dump Dump() { - return new Dump { { DumpContent, PersistenceUtilities.EncodeBase64(Bytes) } }; + //return new Dump { { DumpContent, PersistenceUtilities.EncodeBase64(Bytes) } }; + return null; } - public override void LoadDump(Dump dump) + public void LoadDump(Dump dump) { + /* string contentString = dump[DumpContent] as string; if (contentString == null) @@ -85,6 +87,7 @@ public override void LoadDump(Dump dump) } Bytes = PersistenceUtilities.DecodeBase64ToBinary(contentString); + */ } public List AsParts(GlobalPath path, string prefix) @@ -150,10 +153,5 @@ public override int GetHashCode() { return Bytes.GetHashCode(); } - - public override string ToString() - { - return "File content"; - } } } \ No newline at end of file diff --git a/src/kOS.Safe/Persistence/PathValue.cs b/src/kOS.Safe/Persistence/PathValue.cs index 6c10d9c0ce..971d555dfb 100644 --- a/src/kOS.Safe/Persistence/PathValue.cs +++ b/src/kOS.Safe/Persistence/PathValue.cs @@ -16,7 +16,7 @@ namespace kOS.Safe /// Instances of this class are on the other hand created only for the user. /// [kOS.Safe.Utilities.KOSNomenclature("Path")] - public class PathValue : SerializableStructure + public class PathValue : Structure { [Function("path")] public class FunctionPath : SafeFunctionBase @@ -56,29 +56,13 @@ public SafeSharedObjects Shared { } } - // Only used by CreateFromDump() and other peer constructors. - // Don't make it public because it leaves fields - // unpopulated: - private PathValue() + public PathValue(GlobalPath path, SafeSharedObjects sharedObjects) { InitializeSuffixes(); - } - - public PathValue(GlobalPath path, SafeSharedObjects sharedObjects) : this() - { Path = path; this.sharedObjects = sharedObjects; } - // Required for all IDumpers for them to work, but can't enforced by the interface because it's static: - public static PathValue CreateFromDump(SafeSharedObjects shared, Dump d) - { - var newObj = new PathValue(); - newObj.Shared = shared; - newObj.LoadDump(d); - return newObj; - } - public PathValue FromPath(GlobalPath path) { return new PathValue(path, sharedObjects); @@ -119,19 +103,15 @@ public PathValue Combine(params StringValue[] segments) return FromPath(Path.Combine(segments.Select(s => s.ToString()).ToArray())); } - public override Dump Dump() - { - return new Dump { { DumpPath, Path.ToString() } }; - } - - public override void LoadDump(Dump dump) + public Dump Dump() { - Path = GlobalPath.FromString(dump[DumpPath] as string); + //return new Dump { { DumpPath, Path.ToString() } }; + return null; } - public override string ToString() + public void LoadDump(Dump dump) { - return Path.ToString(); + //Path = GlobalPath.FromString(dump[DumpPath] as string); } public override bool Equals(object other) diff --git a/src/kOS.Safe/Persistence/Volume.cs b/src/kOS.Safe/Persistence/Volume.cs index d4127657ff..cdcfa65c7e 100644 --- a/src/kOS.Safe/Persistence/Volume.cs +++ b/src/kOS.Safe/Persistence/Volume.cs @@ -213,10 +213,12 @@ public Lexicon ListAsLexicon() return Root.ListAsLexicon(); } + /* public override string ToString() { return "Volume(" + Name + ", " + Capacity + ")"; } + */ private void InitializeVolumeSuffixes() { diff --git a/src/kOS.Safe/Persistence/VolumeFile.cs b/src/kOS.Safe/Persistence/VolumeFile.cs index 9fe1f685bf..ca6becc8b0 100644 --- a/src/kOS.Safe/Persistence/VolumeFile.cs +++ b/src/kOS.Safe/Persistence/VolumeFile.cs @@ -29,10 +29,12 @@ public bool WriteLn(string content) public abstract void Clear(); + /* public override string ToString() { return Name; } + */ private void InitializeSuffixes() { diff --git a/src/kOS.Safe/Persistence/VolumeItem.cs b/src/kOS.Safe/Persistence/VolumeItem.cs index 487c80d4ab..aa1d932339 100644 --- a/src/kOS.Safe/Persistence/VolumeItem.cs +++ b/src/kOS.Safe/Persistence/VolumeItem.cs @@ -1,4 +1,4 @@ -using System; +using System; using kOS.Safe.Persistence; using System.Linq; using kOS.Safe.Encapsulation; @@ -52,10 +52,12 @@ private void InitializeSuffixes() AddSuffix("ISFILE", new Suffix(() => this is VolumeFile)); } + /* public override string ToString() { return string.IsNullOrEmpty(Path.Name) ? "Root directory" : Path.Name; } + */ public abstract int Size { get; } } diff --git a/src/kOS.Safe/Serialization/Dump.cs b/src/kOS.Safe/Serialization/Dump.cs index 7480189c69..eb45549834 100644 --- a/src/kOS.Safe/Serialization/Dump.cs +++ b/src/kOS.Safe/Serialization/Dump.cs @@ -1,22 +1,485 @@ -using System; +using System; using System.Collections.Generic; +using kOS.Safe.Encapsulation; +using kOS.Safe.Serialization; +using kOS.Safe.Exceptions; +using System.Reflection; +using System.Linq; namespace kOS.Safe { - public class Dump : Dictionary + public class Dump { - public const string Items = "items"; - public const string Entries = "entries"; + public virtual bool IsSerializable { get { return false; } } + public virtual Dictionary DebugInfo { get; private set; } public Dump() { + DebugInfo = new Dictionary(); } - public Dump(IDictionary dictionary) : base(dictionary) + public virtual JsonObject ToJsonObject() { + throw new NotImplementedException(); } + public virtual void WriteReadable(IndentedStringBuilder sb) + { + sb.Append(""); + } + + public virtual Structure ToStructure(SafeSharedObjects sharedObjects) + { + return null; + } + + public static Dump FromJson(JsonObject json) + { + string typename = json["$type"] as string; + if (typename == null) + throw new KOSSerializationException("Unable to deserialize object without $type string."); + + var destinationTypes = AppDomain.CurrentDomain.GetAssemblies(). + Where(a => !a.IsDynamic). + Select(a => a.GetType(typename)). + ToList(); + if (destinationTypes.Count > 1) + throw new KOSYouShouldNeverSeeThisException("Multiple definitions found of " + typename); + if (!destinationTypes.Any()) + throw new KOSSerializationException("Unable to deserialize class with type " + typename); + Type destinationType = destinationTypes.First(); + + Type dumpType = null; + + foreach (var method in destinationType.GetMethods()) + { + if (!method.IsStatic) + continue; + + var printAttr = method.GetCustomAttribute(); + if (printAttr == null) + continue; + + var parameters = method.GetParameters(); + if (parameters.Length != 2) + throw new KOSYouShouldNeverSeeThisException("PrintDump function defined with wrong amount of parameters."); + + + dumpType = parameters[0].ParameterType; + break; + } + + if (dumpType == null) + throw new KOSSerializationException(string.Format("Objects of type {0} do not support deserialization.", typename)); + + if (!dumpType.IsSubclassOf(typeof(DeserializableDump))) + throw new KOSYouShouldNeverSeeThisException("PrintDump function uses non-dump type as first argument."); + + // TODO, do switch statement and call static CreateFromJson + return null; + } } -} + public class DumpOpaque : Dump + { + public DumpOpaque(string classname) + { + this.classname = classname; + } + + private string classname; + + public override void WriteReadable(IndentedStringBuilder sb) + { + sb.Append("<"); + sb.Append(classname); + sb.Append(">"); + } + } + + public class DumpRecursionPlaceholder : Dump + { + public override void WriteReadable(IndentedStringBuilder sb) + { + sb.Append(""); + } + } + + public abstract class DeserializableDump : Dump + { + protected Type deserializer { get; private set; } + protected Delegate deserializerDelegate { get; private set; } + protected Delegate printerDelegate { get; private set; } + + public DeserializableDump(Type deserializer) + { + if (deserializer == null) + throw new ArgumentNullException("Deserializer cannot be null", "deserializer"); + this.deserializer = deserializer; + + MethodInfo deserializeMethod = null; + MethodInfo printMethod = null; + + var methods = deserializer.GetMethods(); + foreach (var method in methods) + { + if (!method.IsStatic) + continue; + + var printAttr = method.GetCustomAttribute(); + var deserializeAttr = method.GetCustomAttribute(); + + if (printAttr != null) + { + if (printMethod != null) + throw new KOSYouShouldNeverSeeThisException("Duplicate PrintDump function defined on " + deserializer.FullName); + if (method.GetParameters().Length != 2) + throw new KOSYouShouldNeverSeeThisException("PrintDump function defined with incorrect number of arguments."); + printMethod = method; + } + + if (deserializeAttr != null) + { + if (deserializeMethod != null) + throw new KOSYouShouldNeverSeeThisException("Duplicate DeserializeDump function defined on " + deserializer.FullName); + if (method.GetParameters().Length != 2) + throw new KOSYouShouldNeverSeeThisException("DeserializeDump function defined with incorrect number of arguments."); + deserializeMethod = method; + } + } + + if (printMethod == null) + throw new KOSYouShouldNeverSeeThisException("Dump created without corresponding print method in: " + deserializer.FullName); + + var printArg = printMethod.GetParameters()[0]; + + if (deserializeMethod != null) + { + var deserializeArg = deserializeMethod.GetParameters()[0]; + if (printArg.ParameterType != deserializeArg.ParameterType) + throw new KOSYouShouldNeverSeeThisException("Deserializable class contains conflicting Dump types in PrintDump and DeserializeDump: " + deserializer.FullName); + } + + if (printArg.ParameterType != GetType()) + throw new KOSYouShouldNeverSeeThisException(String.Format( + "Deserializable class {0} expects to print using {1} but created a {2}.", + deserializer.FullName, + printArg.ParameterType.FullName, + GetType().FullName + )); + + //void Print(T dump, IndentedStringBuilder sb) + Type printType = typeof(Action<,>).MakeGenericType(printArg.ParameterType, typeof(IndentedStringBuilder)); + //StringValue CreateFromDump(T d, SafeSharedObjects shared) + Type deserializeType = typeof(Func<,,>).MakeGenericType(printArg.ParameterType, typeof(SafeSharedObjects), deserializer); + + try + { + printerDelegate = printMethod.CreateDelegate(printType); + } + catch (System.ArgumentException) { } + if (printerDelegate == null) + throw new KOSYouShouldNeverSeeThisException(string.Format( + "Class {0} defines a Printer function with the wrong type. Please use void Print({1} dump, IndentedStringBuilder sb).", + deserializer.FullName, + printArg.ParameterType.FullName + )); + + if (deserializeMethod != null) + { + try { + deserializerDelegate = deserializeMethod.CreateDelegate(deserializeType); + } + catch (System.ArgumentException) { } + + if (deserializerDelegate == null) + throw new KOSYouShouldNeverSeeThisException(string.Format( + "Class {0} defines a Deserialization function with the wrong type. Please use {0} CreateFromDump(SafeSharedObjects shared, {1} d)", + deserializer.FullName, + printArg.ParameterType.FullName + )); + } + } + + protected Structure Deserialize(T dump, SafeSharedObjects safeSharedObjects) where T : Dump + { + if (typeof(T) != GetType()) + throw new KOSYouShouldNeverSeeThisException("Tried to deserialize with the wrong Dump type."); + + var func = (Func)deserializerDelegate; + return func(dump, safeSharedObjects); + } + + protected void Print(T dump, IndentedStringBuilder sb) where T : Dump + { + if (typeof(T) != GetType()) + throw new KOSYouShouldNeverSeeThisException("Tried to deserialize with the wrong Dump type."); + + var func = (Action)printerDelegate; + func(dump, sb); + } + } + + public class DumpDictionary : DeserializableDump + { + public DumpDictionary(Type deserializer) : base(deserializer) { } + + private Dictionary dumpItems = new Dictionary(); + private Dictionary primitiveItems = new Dictionary(); + private bool hasUnserializableChildren = false; + public override bool IsSerializable { get { return !hasUnserializableChildren; } } + public void Add(string key, IDumper value, IDumperContext c) + { + if (key == null) + throw new ArgumentNullException("Unable to add items to a null key", "key"); + if (key == "items" || key == "entries") + throw new ArgumentException(String.Format("Key cannot be {0}. Please use a different key or consider using DumpList or DumpLexicon.", key), "key"); + if (dumpItems.ContainsKey(key) || primitiveItems.ContainsKey(key)) + throw new ArgumentException("An element with the same key already exists in this Dictionary.", "key"); + if (value == null) + throw new ArgumentNullException("Unable to add null value to dump", "value"); + if (c == null) + throw new ArgumentNullException("A context is required to convert objects into Dumps.", "c"); + + var valueDump = c.Convert(value); + + hasUnserializableChildren = hasUnserializableChildren || !valueDump.IsSerializable; + + dumpItems.Add(key, valueDump); + } + + public void Add(string key, double value) + { + if (key == null) + throw new ArgumentNullException("Unable to add items to a null key", "key"); + if (key == "items" || key == "entries") + throw new ArgumentException(String.Format("Key cannot be {0}. Please use a different key or consider using DumpList or DumpLexicon.", key), "key"); + if (dumpItems.ContainsKey(key) || primitiveItems.ContainsKey(key)) + throw new ArgumentException("An element with the same key already exists in this Dictionary.", "key"); + + primitiveItems.Add(key, value); + } + public void Add(string key, bool value) + { + if (key == null) + throw new ArgumentNullException("Unable to add items to a null key", "key"); + if (key == "items" || key == "entries") + throw new ArgumentException(String.Format("Key cannot be {0}. Please use a different key or consider using DumpList or DumpLexicon.", key), "key"); + if (dumpItems.ContainsKey(key) || primitiveItems.ContainsKey(key)) + throw new ArgumentException("An element with the same key already exists in this Dictionary.", "key"); + + primitiveItems.Add(key, value); + } + + public void Add(string key, string value) + { + if (key == null) + throw new ArgumentNullException("Unable to add items to a null key", "key"); + if (dumpItems.ContainsKey(key) || primitiveItems.ContainsKey(key)) + throw new ArgumentException("An element with the same key already exists in this Dictionary.", "key"); + + primitiveItems.Add(key, value); + } + + public string GetString(string key) + { + if (!primitiveItems.ContainsKey(key)) + throw new KOSSerializationException(string.Format("Missing key {0} when trying to parse {1}", key, deserializer.Name)); + + if (primitiveItems[key] is string) + return (string)primitiveItems[key]; + + throw new KOSSerializationException(string.Format("Key {0} was expected to be a string but is actually a {1}.", key, primitiveItems[key].GetType().Name)); + } + public double GetDouble(string key) + { + if (!primitiveItems.ContainsKey(key)) + throw new KOSSerializationException(string.Format("Missing key {0} when trying to parse {1}", key, deserializer.Name)); + + if (primitiveItems[key] is double) + return (double)primitiveItems[key]; + + throw new KOSSerializationException(string.Format("Key {0} was expected to be a double but is actually a {1}.", key, primitiveItems[key].GetType().Name)); + } + + public bool GetBool(string key) + { + if (!primitiveItems.ContainsKey(key)) + throw new KOSSerializationException(string.Format("Missing key {0} when trying to parse {1}", key, deserializer.Name)); + + if (primitiveItems[key] is bool) + return (bool)primitiveItems[key]; + + throw new KOSSerializationException(string.Format("Key {0} was expected to be a bool but is actually a {1}.", key, primitiveItems[key].GetType().Name)); + } + + public Dump GetDump(string key) + { + if (!dumpItems.ContainsKey(key)) + throw new KOSSerializationException(string.Format("Missing key {0} when trying to parse {1}", key, deserializer.Name)); + + return dumpItems[key]; + } + + public Structure GetStructure(string key, SafeSharedObjects sharedObjects) + { + return GetDump(key).ToStructure(sharedObjects); + } + public override JsonObject ToJsonObject() + { + var result = new JsonObject(); + + foreach (var primitiveKv in primitiveItems) + result.Add(primitiveKv.Key, primitiveKv.Value); + foreach (var dumpKv in dumpItems) + result.Add(dumpKv.Key, dumpKv.Value.ToJsonObject()); + + result.Add("$type", deserializer.FullName); + return result; + } + + public override void WriteReadable(IndentedStringBuilder sb) + { + Print(this, sb); + } + + public override Structure ToStructure(SafeSharedObjects sharedObjects) + { + return Deserialize(this, sharedObjects); + } + } + public class DumpList : DeserializableDump + { + public DumpList(Type deserializer) : base(deserializer) { } + + public override bool IsSerializable { get { return !hasUnserializableChildren; } } + public int Count { get { return items.Count; } } + + private List items = new List(); + private bool hasUnserializableChildren = false; + + public void Add(IDumper value, IDumperContext c) + { + if (value == null) + throw new ArgumentNullException("Unable to add null value to dump", "value"); + if (c == null) + throw new ArgumentNullException("A context is required to convert objects into Dumps.", "c"); + + var valueDump = c.Convert(value); + + hasUnserializableChildren = hasUnserializableChildren || !valueDump.IsSerializable; + + items.Add(valueDump); + } + + public Dump this[int i] + { + get + { + return items[i]; + } + } + + public Structure GetDeserialized(int i, SafeSharedObjects sharedObjects) + { + return items[i].ToStructure(sharedObjects); + } + + public override JsonObject ToJsonObject() + { + var result = new JsonObject(); + + var arr = new JsonArray(); + result.Add("items", arr); + + foreach (var i in items) + { + arr.Add(i.ToJsonObject()); + } + + result.Add("$type", deserializer.FullName); + return result; + } + + public override void WriteReadable(IndentedStringBuilder sb) + { + Print(this, sb); + } + } + + public class DumpLexicon : DeserializableDump + { + public DumpLexicon(Type deserializer) : base(deserializer) { } + + public int Count { get { return keys.Count; } } + public override bool IsSerializable { get { return !hasUnserializableChildren; } } + + + private List values = new List(); + private List keys = new List(); + private bool hasUnserializableChildren = false; + + public void Add(IDumper key, IDumper value, IDumperContext c) + { + if (key == null) + throw new ArgumentNullException("Unable to add null key to dump", "key"); + if (value == null) + throw new ArgumentNullException("Unable to add null value to dump", "value"); + if (c == null) + throw new ArgumentNullException("A context is required to convert objects into Dumps.", "c"); + + var valueDump = c.Convert(value); + var keyDump = c.Convert(key); + + hasUnserializableChildren = hasUnserializableChildren || !valueDump.IsSerializable; + + values.Add(valueDump); + keys.Add(keyDump); + } + + public IEnumerable> GetItems() + { + var result = new List>(); + + for (int i = 0; i < values.Count; i++) + { + result.Add(new KeyValuePair(keys[i], values[i])); + } + + return result; + } + public IEnumerable> GetStructures(SafeSharedObjects sharedObjects) + { + var result = new List>(); + + for (int i = 0; i < values.Count; i++) + { + result.Add(new KeyValuePair(keys[i].ToStructure(sharedObjects), values[i].ToStructure(sharedObjects))); + } + + return result; + } + + public override JsonObject ToJsonObject() + { + var result = new JsonObject(); + + var entries = new JsonArray(); + result.Add("entries", entries); + + foreach(var kv in GetItems()) + { + entries.Add(kv.Key.ToJsonObject()); + entries.Add(kv.Value.ToJsonObject()); + } + + result.Add("$type", deserializer.FullName); + return result; + } + public override void WriteReadable(IndentedStringBuilder sb) + { + Print(this, sb); + } + } +} diff --git a/src/kOS.Safe/Serialization/DumpWithHeader.cs b/src/kOS.Safe/Serialization/DumpWithHeader.cs deleted file mode 100644 index e2275021e5..0000000000 --- a/src/kOS.Safe/Serialization/DumpWithHeader.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Collections.Generic; - -namespace kOS.Safe.Serialization -{ - public class DumpWithHeader : Dump - { - public string Header { get; set; } - - public DumpWithHeader() - { - } - - public DumpWithHeader(IDictionary dictionary) : base(dictionary) - { - } - } -} - diff --git a/src/kOS.Safe/Serialization/Formatter.cs b/src/kOS.Safe/Serialization/Formatter.cs deleted file mode 100644 index 81e1d3a3e8..0000000000 --- a/src/kOS.Safe/Serialization/Formatter.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Collections.Generic; - -namespace kOS.Safe.Serialization -{ - public interface IFormatWriter - { - string Write(Dump value); - } - - public interface IFormatReader - { - Dump Read(string input); - } -} - diff --git a/src/kOS.Safe/Serialization/IDumper.cs b/src/kOS.Safe/Serialization/IDumper.cs index e18b8d5e04..fd99f50c54 100644 --- a/src/kOS.Safe/Serialization/IDumper.cs +++ b/src/kOS.Safe/Serialization/IDumper.cs @@ -1,35 +1,71 @@ using System; +using System.Collections.Generic; +using System.Linq; +using kOS.Safe.Exceptions; namespace kOS.Safe.Serialization { - /// - /// Classes implementing this interface can dump their data to a dictionary. - /// - /// Dumps should only contain primitives, strings, lists and other Dumps. - /// SerializationMgr, for convenience, will handle any encapsulation types that implement - /// PrimitiveStructure when serializing. - /// - /// Types implementing IDumper should make sure that proper encapsulation types are created in LoadDump whenever - /// necessary. - /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)] + public class DumpDeserializer : Attribute { } + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)] + public class DumpPrinter : Attribute { } + + public interface IDumperContext : IDisposable + { + Dump Convert(IDumper conversionTarget); + } + + public class DumperState + { + private class DumperContext : IDumperContext + { + private DumperState state; + private object contextHolder; + private bool disposed = false; + public DumperContext(DumperState state, object contextHolder) + { + if (contextHolder == null) + throw new ArgumentNullException("Context holder cannot be null", "contextHolder"); + + this.state = state; + this.contextHolder = contextHolder; + + state.seenList.Add(contextHolder); + } + + public void Dispose() + { + if (this.disposed) + return; + + var lastItem = state.seenList.Last(); + if (lastItem != contextHolder) + throw new KOSYouShouldNeverSeeThisException("Context accounting failure during serialization."); + + state.seenList.RemoveAt(state.seenList.Count - 1); + } + + public Dump Convert(IDumper conversionTarget) + { + if (state.seenList.Contains(conversionTarget)) + return new DumpRecursionPlaceholder(); + return conversionTarget.Dump(state); + } + } + public DumperState() + { + seenList = new List(); + } + + private List seenList; + public IDumperContext Context(IDumper contextHolder) + { + return new DumperContext(this, contextHolder); + } + } + public interface IDumper { - Dump Dump(); - void LoadDump(Dump dump); - - // Here is a limitation of C#'s inheritence model. It would be good to force all - // implementers of IDumper (except abstract classes, as they cannot be "instanced") - // to have this method - but it's a static method, so we can't. All IDumper's should implement - // this static method. We may make some ad-hoc reflection walk to enforce this rule since - // the compiler cannot: - // - // /// Creates an instance of from the Dump - // /// passed in. If this object cares about needing the reference to Shared, it can use - // /// the parameter for that, or it is free to throw that away if it doesn't care. - // /// This method should essentially both construct the object and populate it with - // /// the LoadDump() method above. - // /// - // static CreateFromDump(SafeSharedObjects shared, Dump d) - // + Dump Dump(DumperState s); } } diff --git a/src/kOS.Safe/Serialization/IndentedStringBuilder.cs b/src/kOS.Safe/Serialization/IndentedStringBuilder.cs new file mode 100644 index 0000000000..04f8243039 --- /dev/null +++ b/src/kOS.Safe/Serialization/IndentedStringBuilder.cs @@ -0,0 +1,101 @@ +using System; +using System.Text; +using System.Linq; +using System.Collections.Generic; + +namespace kOS.Safe +{ + public interface IIndentedStringBuilderIndent : IDisposable { } + + public class IndentedStringBuilder + { + private class IndentedStringBuilderIndent : IIndentedStringBuilderIndent + { + private IndentedStringBuilder stringBuilder; + private int indentSize; + private bool disposed = false; + public IndentedStringBuilderIndent(IndentedStringBuilder sb, int indentSize) + { + this.stringBuilder = sb; + this.indentSize = indentSize; + + sb.indentation += indentSize; + } + + public void Dispose() + { + if (this.disposed) + return; + stringBuilder.indentation -= indentSize; + this.disposed = true; + } + } + + protected StringBuilder stringBuilder; + private int indentation; + private bool startOfLine; + + public IndentedStringBuilder() + { + stringBuilder = new StringBuilder(); + startOfLine = true; + } + + public IIndentedStringBuilderIndent Indent(int size = 2) + { + return new IndentedStringBuilderIndent(this, size); + } + + protected virtual void AppendSingleLineOnly(string line) + { + if (startOfLine) + { + for (int i = 0; i < indentation; i++) + stringBuilder.Append(' '); + startOfLine = false; + } + + stringBuilder.Append(line); + + } + public void Append(string value) + { + var lines = value.Split('\n'); + AppendSingleLineOnly(lines[0]); + + foreach (var line in lines.Skip(1)) { + AppendLine(); + AppendSingleLineOnly(line); + } + } + public virtual void AppendLine() + { + stringBuilder.AppendLine(); + startOfLine = true; + } + + public override string ToString() + { + return stringBuilder.ToString(); + } + } + + public class SingleLineIndentedStringBuilder : IndentedStringBuilder + { + private bool eol = false; + public bool IsSingleLine { get { return !eol; } } + + protected override void AppendSingleLineOnly(string line) + { + if (eol) + return; + stringBuilder.Append(line); + } + + public override void AppendLine() + { + eol = true; + } + } +} + diff --git a/src/kOS.Safe/Serialization/JSONFormatter.cs b/src/kOS.Safe/Serialization/JSONFormatter.cs index 07d74f71e6..c525ab7a91 100644 --- a/src/kOS.Safe/Serialization/JSONFormatter.cs +++ b/src/kOS.Safe/Serialization/JSONFormatter.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System; using System.Linq; using System.Text; @@ -6,35 +6,8 @@ namespace kOS.Safe.Serialization { - public class JsonFormatter : IFormatWriter, IFormatReader + public class JsonFormatter { - private static readonly JsonFormatter instance; - - public static IFormatReader ReaderInstance - { - get - { - return instance; - } - } - - public static IFormatWriter WriterInstance - { - get - { - return instance; - } - } - - private JsonFormatter() - { - } - - static JsonFormatter() - { - instance = new JsonFormatter(); - } - private object WriteJsonObjects(object value) { var objects = value as IDictionary; @@ -56,18 +29,18 @@ private object WriteJsonObjects(object value) public string Write(Dump value) { - return JsonHelper.FormatJson(SimpleJson.SerializeObject(WriteJsonObjects(value))); + return JsonHelper.FormatJson(SimpleJson.SerializeObject(WriteJsonObjects(value.ToJsonObject()))); } private Dump ReadJsonObject(JsonObject dictionary) { var result = new Dump(); - + /* foreach (var entry in dictionary) { result[entry.Key] = ReadValue(entry.Value); } - + */ return result; } diff --git a/src/kOS.Safe/Serialization/SafeSerializationMgr.cs b/src/kOS.Safe/Serialization/SafeSerializationMgr.cs deleted file mode 100644 index 8c2470cef5..0000000000 --- a/src/kOS.Safe/Serialization/SafeSerializationMgr.cs +++ /dev/null @@ -1,228 +0,0 @@ -using System; -using System.Text; -using System.Collections.Generic; -using kOS.Safe.Encapsulation; -using kOS.Safe.Exceptions; -using System.Linq; -using System.Reflection; -using kOS.Safe.Utilities; - -namespace kOS.Safe.Serialization -{ - public class SafeSerializationMgr - { - public static string TYPE_KEY = "$type"; - private static HashSet assemblies = new HashSet(); - - private readonly SafeSharedObjects safeSharedObjects; - - public SafeSerializationMgr(SafeSharedObjects sharedObjects) - { - this.safeSharedObjects = sharedObjects; - } - - public static void AddAssembly(string assembly) - { - assemblies.Add(assembly); - } - - public static bool IsSerializablePrimitive(object serialized) - { - return serialized.GetType().IsPrimitive || serialized is string || IsPrimitiveStructure(serialized); - } - - public static bool IsPrimitiveStructure(object serialized) - { - return serialized is PrimitiveStructure; - } - - private object DumpValue(object value, bool includeType) - { - var valueDumper = value as IDumper; - - if (valueDumper != null) { - return Dump(valueDumper, includeType); - } else if (value is Dump) { - return value; - } else if (value is List) { - return (value as List).Select((v) => DumpValue(v, includeType)).ToList(); - } else if (IsSerializablePrimitive(value)) { - return Structure.ToPrimitive(value); - } else { - return value.ToString(); - } - } - - public Dump Dump(IDumper dumper, bool includeType = true) - { - var dump = dumper.Dump(); - - List keys = new List(dump.Keys); - - foreach (object key in keys) - { - dump[key] = DumpValue(dump[key], includeType); - } - - if (includeType) - { - dump.Add(TYPE_KEY, dumper.GetType().Namespace + "." + dumper.GetType().Name); - } - - return dump; - } - - public string Serialize(IDumper serialized, IFormatWriter formatter, bool includeType = true) - { - return formatter.Write(Dump(serialized, includeType)); - } - - public object CreateValue(object value) - { - var objects = value as Dump; - if (objects != null) - { - return CreateFromDump(objects); - } else if (value is List) - { - return (value as List).Select(item => CreateValue(item)).ToList(); - } - - return value; - } - - public IDumper CreateFromDump(Dump dump) - { - var data = new Dump(); - - foreach (KeyValuePair entry in dump) - { - if (entry.Key.Equals(TYPE_KEY)) - { - continue; - } - - data[entry.Key] = CreateValue(entry.Value); - } - - if (!dump.ContainsKey(TYPE_KEY)) - { - throw new KOSSerializationException("Type information missing"); - } - - string typeFullName = dump[TYPE_KEY] as string; - - return CreateAndLoad(typeFullName, data); - } - - public virtual IDumper CreateAndLoad(string typeFullName, Dump data) - { - Type loadedType = GetTypeFromFullname(typeFullName); - Type[] paramSignature = new Type[] { typeof(SafeSharedObjects), typeof(Dump) }; - MethodInfo method = loadedType.GetMethod("CreateFromDump", BindingFlags.Public | BindingFlags.Static, null, paramSignature, null); - IDumper instance; - try - { - instance = (IDumper)method.Invoke(null, new object[] { safeSharedObjects, data }); - } - catch (TargetInvocationException reflectiveCallException) - { - // When you call a method via reflection with MethodInfo.Invoke(), - // it hides any exceptions that method tried to throw inside its own - // wrapper called TargetInvocationException. That would mask our - // intended error messages to the user if we didn't re-throw the actual - // exception the method wanted to generate like so: - throw reflectiveCallException.InnerException; - } - return instance; - } - - protected virtual Type GetTypeFromFullname(string typeFullName) - { - var deserializedType = Type.GetType(typeFullName); - - if (deserializedType == null) - { - foreach (string assembly in assemblies) - { - deserializedType = Type.GetType(typeFullName + ", " + assembly); - if (deserializedType != null) - { - break; - } - } - } - return deserializedType; - } - - static bool staticsAlreadyChecked= false; - /// - /// Ensure all classes implementing IDumper have the required static method in - /// them, which is something the compiler cannot enforce itself because an Interface - /// can't contain static things. - /// - /// Since interfaces don't enforce static things, this enforcement - /// is being done via this Reflection check upon loading that will make nag messages - /// if the CreateFromDump is missing on one of the classes. - /// Note, if we ever need this check elsewhere for another "static" thing in an interface, - /// It should be possible to make this more generic and make a library method here that - /// takes any interface name and any method name and checks to see if it exists. - public static void CheckIDumperStatics() - { - if (staticsAlreadyChecked) - return; - - StringBuilder message = new StringBuilder(1000); - - Type dumperInterface = typeof(IDumper); - - // It's ugly to be checking ALL KSP mods here, but just in case someone - // extends kOS in another mod, it's not safe to check ONLY kOS.dll and kOS.Safe.dll: - Assembly[] allKSPAssemblies = AppDomain.CurrentDomain.GetAssemblies(); - - // All the classes in which the class is derived from IDumper, and instances of the class are - // actually constructable because it isn't Abstract: - IEnumerable iDumperClasses = allKSPAssemblies.SelectMany(a => ReflectUtil.GetLoadedTypes(a)).Where( - b => dumperInterface.IsAssignableFrom(b) && b.IsClass && !b.IsAbstract); - - List offendingClasses = new List(); - Type[] paramSignature = new Type[] { typeof(SafeSharedObjects), typeof(Dump) }; - foreach (Type t in iDumperClasses) - { - if (t.GetMethod("CreateFromDump", BindingFlags.Public | BindingFlags.Static, null, paramSignature, null) == null) - offendingClasses.Add(t.FullName); - } - - if (offendingClasses.Count() > 0) - { - message.Append( - "kOS DEV TEAM ERROR.\n " + - " This is a kOS source problem that the compiler\n" + - " cannot check for because of limitations in C#.\n" + - " (So we check it at runtime and give this error\n" + - " if it's detected.)\n" + - " \n" + - " The following class(es) implement IDumper, but\n" + - " lack the required CreateFromDump() static method\n" + - " that we need all IDumper's to have:\n" + - " "); - message.Append(string.Join(", ", offendingClasses.ToArray())); - Debug.AddNagMessage(Debug.NagType.NAGFOREVER, message.ToString()); - } - staticsAlreadyChecked = true; - } - - public IDumper Deserialize(string input, IFormatReader formatter) - { - Dump dump = formatter.Read(input); - - return dump == null ? null : CreateFromDump(dump); - } - - public string ToString(IDumper dumper) - { - return Serialize(dumper, TerminalFormatter.Instance, false); - } - } -} - diff --git a/src/kOS.Safe/Serialization/SerializableStructure.cs b/src/kOS.Safe/Serialization/SerializableStructure.cs deleted file mode 100644 index 13bbe8aa52..0000000000 --- a/src/kOS.Safe/Serialization/SerializableStructure.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using kOS.Safe.Encapsulation; - -namespace kOS.Safe.Serialization -{ - [kOS.Safe.Utilities.KOSNomenclature("Structure", KOSToCSharp = false)] // reports itself as "Structure" but won't be the canonical meaning of "Structure" - public abstract class SerializableStructure : Structure, IDumper - { - public abstract Dump Dump(); - public abstract void LoadDump(Dump dump); - } -} - diff --git a/src/kOS.Safe/Serialization/TerminalFormatter.cs b/src/kOS.Safe/Serialization/TerminalFormatter.cs deleted file mode 100644 index b14448b4af..0000000000 --- a/src/kOS.Safe/Serialization/TerminalFormatter.cs +++ /dev/null @@ -1,132 +0,0 @@ -using System; -using System.Collections.Generic; -using kOS.Safe.Encapsulation; -using System.Linq; - -namespace kOS.Safe.Serialization -{ - public class TerminalFormatter : IFormatWriter - { - private static int INDENT_SPACES = 2; - private static readonly TerminalFormatter instance; - - public static TerminalFormatter Instance - { - get - { - return instance; - } - } - - private TerminalFormatter() - { - - } - - static TerminalFormatter() - { - instance = new TerminalFormatter(); - } - - public string Write(Dump value) - { - string header = ""; - - var withHeader = value as DumpWithHeader; - if (withHeader != null) - { - header = withHeader.Header + Environment.NewLine; - } - - return header + WriteIndented(value); - } - - public string WriteIndented(Dump dump, int level = 0) - { - IDictionary printedDump; - - if (dump.Count == 1 && dump.ContainsKey(Dump.Items)) { - // special handling for enumerables - List list = dump[Dump.Items] as List; - printedDump = list.Select((x, i) => new { Item = x, Index = (object)i }) - .ToDictionary(x => x.Index, x => x.Item); - } else if (dump.Count == 1 && dump.ContainsKey(Dump.Entries)) { - // special handling for lexicons - List list = dump[Dump.Entries] as List; - - printedDump = new Dictionary(); - - for (int i = 0; 2 * i < list.Count; i++) - { - printedDump[list[2 * i]] = list[2 * i + 1]; - } - } else { - printedDump = dump; - } - - return WriteIndentedDump(printedDump, level); - } - - public string WriteIndentedDump(IDictionary dump, int level) - { - var result = new List(); - - foreach (KeyValuePair entry in dump) - { - var line = string.Empty.PadLeft(level * INDENT_SPACES); - var value = entry.Value; - string valueString; - - var objects = value as Dump; - if (objects != null) - { - string header = Environment.NewLine; - - var withHeader = value as DumpWithHeader; - if (withHeader != null) - { - header = withHeader.Header + Environment.NewLine; - } - - valueString = header + WriteIndented(objects, level + 1); - } else - { - valueString = value.ToString(); - } - - if (entry.Key is string || entry.Key is StringValue) - { - line += string.Format("[\"{0}\"] = ", entry.Key); - } else if (entry.Key is Dump) - { - string header = Environment.NewLine; - - var withHeader = entry.Key as DumpWithHeader; - if (withHeader != null) - { - header = withHeader.Header + Environment.NewLine; - } - - string keyString = header + WriteIndented(entry.Key as Dump, level + 1); - line += string.Format("[{0}] = ", keyString); - } else - { - line += string.Format("[{0}] = ", entry.Key.ToString()); - } - - if (entry.Value is string) - { - line += string.Format("\"{0}\"", valueString); - } else - { - line += string.Format("{0}", valueString); - } - - result.Add(line); - } - - return String.Join(Environment.NewLine, result.ToArray()); - } - } -} - diff --git a/src/kOS.Safe/Utilities/AssemblyWalkAttribute.cs b/src/kOS.Safe/Utilities/AssemblyWalkAttribute.cs index 07e6690da9..0a16f5f1f2 100644 --- a/src/kOS.Safe/Utilities/AssemblyWalkAttribute.cs +++ b/src/kOS.Safe/Utilities/AssemblyWalkAttribute.cs @@ -91,7 +91,7 @@ public static void LoadWalkAttributes(Assembly[] assemblies) public static bool CheckMethodParameters(Type baseType, string methodName, params Type[] parameterTypes) { - var managerRegisterMethod = baseType.GetMethod(methodName, BindingFlags.Static | BindingFlags.Public); + var managerRegisterMethod = baseType.GetMethod(methodName, BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy); if (managerRegisterMethod != null) { // Check the register method to make sure it supports both parameters. @@ -212,7 +212,7 @@ public static void WalkAssemblies(Assembly[] assemblies) if (!string.IsNullOrEmpty(walkAttribute.StaticWalkMethod)) { Type managerType = AllWalkAttributes[walkAttribute]; - var walkMethod = managerType.GetMethod(walkAttribute.StaticWalkMethod, BindingFlags.Static | BindingFlags.Public); + var walkMethod = managerType.GetMethod(walkAttribute.StaticWalkMethod, BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy); walkMethod.Invoke(null, null); } SafeHouse.Logger.Log("Attribute " + walkAttribute.ToString() + " loaded " + walkAttribute.LoadedCount + " objects."); @@ -229,7 +229,7 @@ public static void WalkAssembly(Assembly assembly) if (!string.IsNullOrEmpty(walkAttribute.StaticRegisterMethod)) { Type managerType = AllWalkAttributes[walkAttribute]; - var managerRegisterMethod = managerType.GetMethod(walkAttribute.StaticRegisterMethod, BindingFlags.Static | BindingFlags.Public); + var managerRegisterMethod = managerType.GetMethod(walkAttribute.StaticRegisterMethod, BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy); if (managerRegisterMethod != null) { if (walkAttribute.AttributeType != null) diff --git a/src/kOS.Safe/kOS.Safe.csproj b/src/kOS.Safe/kOS.Safe.csproj index f7e5f37a4e..d2463fc749 100644 --- a/src/kOS.Safe/kOS.Safe.csproj +++ b/src/kOS.Safe/kOS.Safe.csproj @@ -61,6 +61,7 @@ + @@ -222,6 +223,7 @@ + @@ -244,15 +246,10 @@ - - - - - diff --git a/src/kOS/Binding/BindingManager.cs b/src/kOS/Binding/BindingManager.cs index 0fbe60006a..1ae72249ae 100644 --- a/src/kOS/Binding/BindingManager.cs +++ b/src/kOS/Binding/BindingManager.cs @@ -1,3 +1,4 @@ +using kOS.Safe; using kOS.Safe.Binding; using kOS.Safe.Utilities; using System; @@ -7,148 +8,24 @@ namespace kOS.Binding { [AssemblyWalk(AttributeType = typeof(BindingAttribute), InherritedType = typeof(SafeBindingBase), StaticRegisterMethod = "RegisterMethod")] - public class BindingManager : IBindingManager + public class BindingManager : BaseBindingManager { - private readonly SharedObjects shared; - private readonly List bindings = new List(); - private readonly Dictionary variables; - - // Note: When we were using .Net 3.5, This used to be a Dictionary rather than a HashSet of pairs. But that had to - // change because of one .Net 4.x change to how reflection on Attributes works. In .Net 3.5, an Attribute called - // [Foo(1,2)] attached to classA was considered un-equal to an attribute with the same values ([Foo(1,2)]) attached - // to classB. But in .Net 4.0, which class the attribute is attached to is no longer part of its equality test, - // therefore both those examples would be "equal" classes because they are the same name Foo with the same paramters (1,2). - // This meant that when we had many classes decorated with exactly the same thing, [Binding("ksp")], these Attributes - // could be unique keys in a Dictionary in .Net 3.5 because they weren't attached to the same class, but in .Net 4.0 - // they became key clashes because they were now considered "equal" and all such Attributes after the first were - // refusing to be stored in the dictionary. - private static readonly HashSet> rawAttributes = new HashSet>(); - private FlightControlManager flightControl; - public BindingManager(SharedObjects shared) - { - variables = new Dictionary(StringComparer.OrdinalIgnoreCase); - this.shared = shared; - this.shared.BindingMgr = this; - } - - public void Load() - { - var contexts = new string[1]; - contexts[0] = "ksp"; - - bindings.Clear(); - variables.Clear(); - flightControl = null; - - foreach (KeyValuePair attrTypePair in rawAttributes) - { - var type = attrTypePair.Value; - if (attrTypePair.Key.Contexts.Any() && !attrTypePair.Key.Contexts.Intersect(contexts).Any()) continue; - var instanceWithABinding = (SafeBindingBase)Activator.CreateInstance(type); - instanceWithABinding.AddTo(shared); - bindings.Add(instanceWithABinding); - - var manager = instanceWithABinding as FlightControlManager; - if (manager != null) - { - flightControl = manager; - } - } - } - - public static void RegisterMethod(BindingAttribute attr, Type type) - { - KeyValuePair attrTypePair = new KeyValuePair(attr, type); - if (attr != null && !rawAttributes.Contains(attrTypePair)) - { - rawAttributes.Add(attrTypePair); - } - } - - public void AddBoundVariable(string name, BindingGetDlg getDelegate, BindingSetDlg setDelegate) - { - BoundVariable variable; - if (variables.ContainsKey(name)) - { - variable = variables[name]; - } - else - { - variable = new BoundVariable - { - Name = name, - }; - variables.Add(name, variable); - shared.Cpu.AddVariable(variable, name, false); - } - - if (getDelegate != null) - variable.Get = getDelegate; - - if (setDelegate != null) - variable.Set = setDelegate; - } - - public void AddGetter(string name, BindingGetDlg dlg) + public BindingManager(SafeSharedObjects shared) : base(shared) { - AddBoundVariable(name, dlg, null); } - public void AddGetter(IEnumerable names, BindingGetDlg dlg) + protected override void LoadInstanceWithABinding(SafeBindingBase instanceWithABinding) { - foreach (var name in names) + var manager = instanceWithABinding as FlightControlManager; + if (manager != null) { - AddBoundVariable(name, dlg, null); + flightControl = manager; } } - public void AddSetter(string name, BindingSetDlg dlg) - { - AddBoundVariable(name, null, dlg); - } - - public void AddSetter(IEnumerable names, BindingSetDlg dlg) - { - foreach (var name in names) - { - AddBoundVariable(name, null, dlg); - } - } - - public bool HasGetter(string name) - { - return variables.ContainsKey(name); - } - - /// - /// Indicates that the binding should not be cached during execution - /// - /// The binding to modify - public void MarkVolatile(string name) - { - variables[name].Volatile = true; - } - - public void PreUpdate() - { - foreach (var variable in variables) - { - variable.Value.ClearCache(); - } - // update the bindings - foreach (var b in bindings) - { - b.Update(); - } - } - - public void PostUpdate() - { - } - - public void ToggleFlyByWire(string paramName, bool enabled) + public override void ToggleFlyByWire(string paramName, bool enabled) { if (flightControl != null) { @@ -156,7 +33,7 @@ public void ToggleFlyByWire(string paramName, bool enabled) } } - public void SelectAutopilotMode(string autopilotMode) + public override void SelectAutopilotMode(string autopilotMode) { if (flightControl != null) { diff --git a/src/kOS/Communication/InterVesselManager.cs b/src/kOS/Communication/InterVesselManager.cs index a5135bc62f..478a4c9534 100644 --- a/src/kOS/Communication/InterVesselManager.cs +++ b/src/kOS/Communication/InterVesselManager.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using kOS.Safe.Encapsulation; using kOS.Safe.Serialization; @@ -14,16 +14,11 @@ public class InterVesselManager : ScenarioModule private static string VesselQueue = "vesselQueue"; private static string MessageQueue = "messageQueue"; + private Dictionary vesselQueueDumps; private Dictionary vesselQueues; public static InterVesselManager Instance { get; private set; } - static InterVesselManager() - { - // normally we do this in SerializationMgr, but KSPScenarios run before we create any instances - SafeSerializationMgr.AddAssembly(typeof(SerializationMgr).Assembly.FullName); - } - public InterVesselManager() { } @@ -31,6 +26,7 @@ public InterVesselManager() public override void OnLoad(ConfigNode node) { Instance = this; + vesselQueueDumps = new Dictionary(); vesselQueues = new Dictionary(); foreach (ConfigNode subNode in node.GetNodes()) @@ -41,14 +37,8 @@ public override void OnLoad(ConfigNode node) ConfigNode queueNode = subNode.GetNode(MessageQueue); - Dump queueDump = ConfigNodeFormatter.Instance.FromConfigNode(queueNode); - - MessageQueue queue = new SafeSerializationMgr(null).CreateFromDump(queueDump) as MessageQueue; - - if (queue.Count() > 0) - { - vesselQueues[id] = queue; - } + DumpList queueDump = (DumpList)DumpList.FromJson(ConfigNodeFormatter.FromConfigNode(queueNode)); + vesselQueueDumps[id] = queueDump; } } } @@ -58,30 +48,48 @@ public override void OnSave(ConfigNode node) foreach (Vessel vessel in FlightGlobals.Vessels) { string id = vessel.id.ToString(); - if (vesselQueues.ContainsKey(id) && vesselQueues[id].Count() > 0) - { - ConfigNode vesselEntry = new ConfigNode(VesselQueue); - vesselEntry.AddValue(Id, id); + if (vesselQueues.ContainsKey(id) && vesselQueues[id].Count() == 0) + continue; - ConfigNode queueNode = ConfigNodeFormatter.Instance.ToConfigNode(new SafeSerializationMgr(null).Dump(vesselQueues[id])); - queueNode.name = MessageQueue; - vesselEntry.AddNode(queueNode); + ConfigNode vesselEntry = new ConfigNode(VesselQueue); + vesselEntry.AddValue(Id, id); - node.AddNode(vesselEntry); - } + Dump dump = null; + if (vesselQueueDumps.ContainsKey(id)) + dump = vesselQueueDumps[id]; + if (vesselQueues.ContainsKey(id)) + dump = vesselQueues[id].Dump(new DumperState()); + + if (dump == null) + continue; + + ConfigNode queueNode = ConfigNodeFormatter.ToConfigNode(dump.ToJsonObject()); + queueNode.name = MessageQueue; + vesselEntry.AddNode(queueNode); + + node.AddNode(vesselEntry); } } public MessageQueueStructure GetQueue(Vessel vessel, SharedObjects sharedObjects) { string vesselId = vessel.id.ToString(); + var queue = new MessageQueue(); - if (!vesselQueues.ContainsKey(vesselId)) + if (vesselQueues.ContainsKey(vesselId)) { - vesselQueues.Add(vesselId, new MessageQueue()); + queue = vesselQueues[vesselId]; + } else + { + if (vesselQueueDumps.ContainsKey(vesselId)) + { + queue.LoadDump(vesselQueueDumps[vesselId]); + vesselQueueDumps.Remove(vesselId); + } + vesselQueues.Add(vesselId, queue); } - return new MessageQueueStructure(vesselQueues[vesselId], sharedObjects); + return new MessageQueueStructure(queue, sharedObjects); } } } diff --git a/src/kOS/Communication/Message.cs b/src/kOS/Communication/Message.cs index 6da8007b18..55f685b48e 100644 --- a/src/kOS/Communication/Message.cs +++ b/src/kOS/Communication/Message.cs @@ -16,33 +16,16 @@ public class Message : BaseMessage public string Vessel { get; set; } public string Processor { get; set; } - public static Message Create(object content, double sentAt, double receivedAt, VesselTarget sender, string processor) - { - if (content is SerializableStructure) - { - return new Message(new SafeSerializationMgr(null).Dump(content as SerializableStructure), sentAt, receivedAt, sender); - } - else if (content is PrimitiveStructure) - { - return new Message(content as PrimitiveStructure, sentAt, receivedAt, sender); - } - else - { - throw new KOSCommunicationException("Only serializable types and primitives can be sent in a message"); - } - } - - // Only used by CreateFromDump() - unsafe to make public because it makes a message where - // the fields aren't populated: - private Message() - : base() + public Message(Structure content, double sentAt, double receivedAt, VesselTarget sender) + : base(content, sentAt, receivedAt) { + Vessel = sender.Guid.ToString(); } - public Message(Dump content, double sentAt, double receivedAt, VesselTarget sender) + private Message(Structure content, double sentAt, double receivedAt, string vesselGUID) : base(content, sentAt, receivedAt) { - Vessel = sender.Guid.ToString(); + Vessel = vesselGUID; } public Message(PrimitiveStructure content, double sentAt, double receivedAt, VesselTarget sender) @@ -51,28 +34,41 @@ public Message(PrimitiveStructure content, double sentAt, double receivedAt, Ves Vessel = sender.Guid.ToString(); } - // Required for all IDumpers for them to work, but can't enforced by the interface because it's static: - public static Message CreateFromDump(SafeSharedObjects shared, Dump d) - { - var newObj = new Message(); - newObj.LoadDump(d); - return newObj; - } - - public override Dump Dump() + public override Dump Dump(DumperState s) { - Dump dump = base.Dump(); + var dump = new DumpDictionary(this.GetType()); - dump.Add(DumpVessel, Vessel); + using (var context = s.Context(this)) + { + dump.Add(DumpSentAt, SentAt); + dump.Add(DumpReceivedAt, ReceivedAt); + dump.Add(DumpContent, Content, context); + dump.Add(DumpVessel, Vessel); + } return dump; } - public override void LoadDump(Dump dump) + [DumpDeserializer] + public static new Message CreateFromDump(DumpDictionary d, SafeSharedObjects shared) { - base.LoadDump(dump); + return new Message(d.GetStructure(DumpContent, shared), d.GetDouble(DumpSentAt), d.GetDouble(DumpReceivedAt), d.GetString(DumpVessel)); + } + + [DumpPrinter] + public static new void Print(DumpDictionary d, IndentedStringBuilder sb) + { + sb.Append("Message [sent: "); + sb.Append(d.GetDouble(DumpSentAt).ToString()); + sb.Append(", received: "); + sb.Append(d.GetDouble(DumpReceivedAt).ToString()); + sb.Append("]:"); - Vessel = dump[DumpVessel] as string; + using (sb.Indent()) + { + var inner = d.GetDump(DumpContent); + inner.WriteReadable(sb); + } } } } diff --git a/src/kOS/Communication/MessageQueue.cs b/src/kOS/Communication/MessageQueue.cs index b8ced8ce72..7046ad7645 100644 --- a/src/kOS/Communication/MessageQueue.cs +++ b/src/kOS/Communication/MessageQueue.cs @@ -7,14 +7,6 @@ public class MessageQueue : GenericMessageQueue(() => new kOS.Suffixed.TimeStamp(Message.SentAt))); @@ -82,34 +66,20 @@ public BooleanValue GetVesselExists() public Structure DeserializeContent() { - if (Message.Content is Dump) - { - return new SerializationMgr(shared).CreateFromDump(Message.Content as Dump) as SerializableStructure; - } - - return Structure.FromPrimitiveWithAssert(Message.Content); - } - - public override string ToString() - { - return "Message(" + Message.Vessel.ToString() + ")"; + return Message.Content; } - public override Dump Dump() + [DumpDeserializer] + public static MessageStructure CreateFromDump(DumpDictionary d, SafeSharedObjects shared) { - Dump dump = new DumpWithHeader - { - Header = "Message" - }; - - dump.Add(DumpMessage, Message); - - return dump; + var message = Message.CreateFromDump(d.GetDump(DumpMessage) as DumpDictionary, shared); + return new MessageStructure(message, shared as SharedObjects); } - public override void LoadDump(Dump dump) + [DumpPrinter] + public static void Print(DumpDictionary d, IndentedStringBuilder sb) { - Message = dump[DumpMessage] as Message; + Message.Print(d, sb); } } } diff --git a/src/kOS/Communication/VesselConnection.cs b/src/kOS/Communication/VesselConnection.cs index d474eb1d80..7f73ea4b07 100644 --- a/src/kOS/Communication/VesselConnection.cs +++ b/src/kOS/Communication/VesselConnection.cs @@ -38,11 +38,6 @@ public VesselConnection(Vessel vessel, SharedObjects shared) : base() this.vessel = vessel; } - public override string ToString() - { - return "VESSEL CONNECTION(" + vessel.vesselName + ")"; - } - protected override BooleanValue SendMessage(Structure content) { if (!Connected) @@ -54,7 +49,7 @@ protected override BooleanValue SendMessage(Structure content) double sentAt = Planetarium.GetUniversalTime(); double receivedAt = sentAt + Delay; - queue.Push(Message.Create(content, sentAt, receivedAt, VesselTarget.CreateOrGetExisting(shared), shared.Processor.Tag)); + queue.Push(new Message(content, sentAt, receivedAt, VesselTarget.CreateOrGetExisting(shared))); return true; } diff --git a/src/kOS/Module/kOSProcessor.cs b/src/kOS/Module/kOSProcessor.cs index de3fa5e2ad..c4bcc6d9bb 100644 --- a/src/kOS/Module/kOSProcessor.cs +++ b/src/kOS/Module/kOSProcessor.cs @@ -1002,7 +1002,6 @@ public override void OnAwake() { Opcode.InitMachineCodeData(); CompiledObject.InitTypeData(); - SafeSerializationMgr.CheckIDumperStatics(); } private void ProcessElectricity(Part partObj, float time) @@ -1163,7 +1162,7 @@ public bool CheckCanBoot() public void Send(Structure content) { double sentAt = Planetarium.GetUniversalTime(); - Messages.Push(Message.Create(content, sentAt, sentAt, VesselTarget.CreateOrGetExisting(shared), Tag)); + Messages.Push(new Message(content, sentAt, sentAt, VesselTarget.CreateOrGetExisting(shared))); } } } diff --git a/src/kOS/Serialization/ConfigNodeFormatter.cs b/src/kOS/Serialization/ConfigNodeFormatter.cs index 16195d7dd9..07bfbc6dc3 100644 --- a/src/kOS/Serialization/ConfigNodeFormatter.cs +++ b/src/kOS/Serialization/ConfigNodeFormatter.cs @@ -1,4 +1,4 @@ -using System; +using System; using kOS.Safe.Serialization; using System.Collections.Generic; using kOS.Safe; @@ -10,33 +10,20 @@ namespace kOS.Serialization { - public class ConfigNodeFormatter : IFormatWriter, IFormatReader + public class ConfigNodeFormatter { private const string ParentNode = ""; private const string ListKey = "$list"; private const string BooleanKey = "$boolean"; private const string ScalarKey = "$scalar"; private const string ValueKey = "$value"; - private static readonly ConfigNodeFormatter instance = new ConfigNodeFormatter(); - public static ConfigNodeFormatter Instance - { - get - { - return instance; - } - } - - private ConfigNodeFormatter() - { - } - - public ConfigNode ToConfigNode(Dump dump) + public static ConfigNode ToConfigNode(JsonObject dump) { return ToConfigNode(ParentNode, dump); } - private ConfigNode ToConfigNode(string name, List list) + private static ConfigNode ToConfigNode(string name, List list) { ConfigNode configNode = new ConfigNode(); configNode.name = name; @@ -58,7 +45,7 @@ private ConfigNode ToConfigNode(string name, List list) return configNode; } - private ConfigNode ScalarToConfigNode(string name, string type, string value) + private static ConfigNode ScalarToConfigNode(string name, string type, string value) { ConfigNode configNode = new ConfigNode(); configNode.name = name; @@ -75,7 +62,7 @@ private ConfigNode ScalarToConfigNode(string name, string type, string value) return configNode; } - private ConfigNode ToConfigNode(string name, Dump dump) + private static ConfigNode ToConfigNode(string name, JsonObject dump) { ConfigNode configNode = new ConfigNode(); @@ -89,11 +76,11 @@ private ConfigNode ToConfigNode(string name, Dump dump) return configNode; } - private void HandleValue(ConfigNode configNode, string key, object value) + private static void HandleValue(ConfigNode configNode, string key, object value) { - if (value is Dump) + if (value is JsonObject) { - configNode.AddNode(key, ToConfigNode(key, value as Dump)); + configNode.AddNode(key, ToConfigNode(key, value as JsonObject)); } else if (value is List) { configNode.AddNode(key, ToConfigNode(key, value as List)); @@ -113,9 +100,9 @@ private void HandleValue(ConfigNode configNode, string key, object value) } } - private List ListFromConfigNode(ConfigNode configNode) + private static JsonArray ListFromConfigNode(ConfigNode configNode) { - List result = new List(); + JsonArray result = new JsonArray(); bool hasMoreValues = true; @@ -136,7 +123,7 @@ private List ListFromConfigNode(ConfigNode configNode) return result; } - private object ObjectFromConfigNode(ConfigNode configNode) + private static object ObjectFromConfigNode(ConfigNode configNode) { if (configNode.HasNode(ListKey)) { @@ -156,9 +143,9 @@ private object ObjectFromConfigNode(ConfigNode configNode) return FromConfigNode(configNode); } - public Dump FromConfigNode(ConfigNode configNode) + public static JsonObject FromConfigNode(ConfigNode configNode) { - Dump result = new Dump(); + JsonObject result = new JsonObject(); foreach (ConfigNode.Value val in configNode.values) { @@ -173,17 +160,17 @@ public Dump FromConfigNode(ConfigNode configNode) return result; } - public string Write(Dump value) - { - return ToConfigNode(value).ToString(); - } + //public static string Write(Dump value) + //{ + // return ToConfigNode(value).ToString(); + //} - public Dump Read(string input) - { - ConfigNode configNode = ConfigNode.Parse(input); + //public static Dump Read(string input) + //{ + // ConfigNode configNode = ConfigNode.Parse(input); - return FromConfigNode(configNode.GetNode(ParentNode)); - } + // return Dump.FromJson(FromConfigNode(configNode.GetNode(ParentNode))); + //} } } diff --git a/src/kOS/Serialization/SerializationMgr.cs b/src/kOS/Serialization/SerializationMgr.cs deleted file mode 100644 index 1b362661d4..0000000000 --- a/src/kOS/Serialization/SerializationMgr.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using System.Reflection; -using kOS.Safe.Serialization; -using kOS.Safe.Encapsulation; -using System.Collections.Generic; -using kOS.Safe.Exceptions; -using kOS.Safe.Utilities; -using kOS.Safe; - -namespace kOS.Serialization -{ - public class SerializationMgr : SafeSerializationMgr - { - private readonly SharedObjects sharedObjects; - - public SerializationMgr(SharedObjects sharedObjects) : base(sharedObjects) - { - SafeSerializationMgr.AddAssembly(typeof(SerializationMgr).Assembly.FullName); - this.sharedObjects = (SharedObjects)sharedObjects; - } - - public override IDumper CreateAndLoad(string typeFullName, Dump data) - { - Type loadedType = base.GetTypeFromFullname(typeFullName); - MethodInfo method = loadedType.GetMethod("CreateFromDump", new Type[] { typeof(SafeSharedObjects), typeof(Dump) }); - IDumper instance = (IDumper)method.Invoke(null, new object[] { sharedObjects, data }); - return instance; - } - } -} - diff --git a/src/kOS/Sound/NoteValue.cs b/src/kOS/Sound/NoteValue.cs index 607926d0c6..fa92800e5b 100644 --- a/src/kOS/Sound/NoteValue.cs +++ b/src/kOS/Sound/NoteValue.cs @@ -12,7 +12,7 @@ namespace kOS.Sound /// [kOS.Safe.Utilities.KOSNomenclature("Note")] [kOS.Safe.Utilities.KOSNomenclature("SlideNote", CSharpToKOS = false)] - public class NoteValue : SerializableStructure + public class NoteValue : Structure { public float Frequency { get; set; } public float EndFrequency { get; set; } @@ -47,20 +47,6 @@ public NoteValue(string letterNote, string endLetterNote, float vol, float keyDo { } - // Only used by CreateFromDump()- don't make it public because it leaves fields - // unpopulated if not immediately followed up by LoadDump(): - private NoteValue() - { - } - - // Required for all IDumpers for them to work, but can't enforced by the interface because it's static: - public static NoteValue CreateFromDump(SafeSharedObjects shared, Dump d) - { - var newObj = new NoteValue(); - newObj.LoadDump(d); - return newObj; - } - private void InitializeSuffixes() { AddSuffix("FREQUENCY", new Suffix(() => Frequency)); @@ -70,19 +56,9 @@ private void InitializeSuffixes() AddSuffix("DURATION", new Suffix(() => Duration)); } - public override string ToString() - { - if (Frequency == EndFrequency) - return String.Format("Note({0},{1},{2},{3})", Frequency, KeyDownLength, Duration, Volume); - else - return String.Format("SlideNote({0},{1},{2},{3},{4})", Frequency, EndFrequency, KeyDownLength, Duration, Volume); - } - - public override Dump Dump() + public override Dump Dump(DumperState s) { - DumpWithHeader result = new DumpWithHeader(); - - result.Header = "NOTE"; + DumpDictionary result = new DumpDictionary(this.GetType()); result.Add("freq", Frequency); result.Add("endfreq", EndFrequency); @@ -93,13 +69,42 @@ public override Dump Dump() return result; } - public override void LoadDump(Dump dump) + [DumpDeserializer] + public static NoteValue CreateFromDump(DumpDictionary d, SafeSharedObjects shared) { - Frequency = Convert.ToSingle(dump["freq"]); - EndFrequency = Convert.ToSingle(dump["endfreq"]); - Volume = Convert.ToSingle(dump["vol"]); - KeyDownLength = Convert.ToSingle(dump["keydown"]); - Duration = Convert.ToSingle(dump["duration"]); + return new NoteValue( + (float)d.GetDouble("freq"), + (float)d.GetDouble("endfreq"), + (float)d.GetDouble("vol"), + (float)d.GetDouble("keydown"), + (float)d.GetDouble("duration") + ); + } + + [DumpPrinter] + public static void Print(DumpDictionary dump, IndentedStringBuilder sb) + { + double freq = dump.GetDouble("freq"); + double endfreq = dump.GetDouble("endfreq"); + bool staticNote = (freq == endfreq); + + sb.Append(staticNote ? "Note(" : "SlideNote("); + sb.Append(freq.ToString()); + sb.Append(","); + if (!staticNote) + { + sb.Append(endfreq.ToString()); + sb.Append(","); + } + + sb.Append(dump.GetDouble("keydown").ToString()); + sb.Append(","); + + sb.Append(dump.GetDouble("duration").ToString()); + sb.Append(","); + + sb.Append(dump.GetDouble("vol").ToString()); + sb.Append(")"); } /// diff --git a/src/kOS/Suffixed/BodyTarget.cs b/src/kOS/Suffixed/BodyTarget.cs index bf46d7a431..a0541be261 100644 --- a/src/kOS/Suffixed/BodyTarget.cs +++ b/src/kOS/Suffixed/BodyTarget.cs @@ -14,7 +14,7 @@ namespace kOS.Suffixed [kOS.Safe.Utilities.KOSNomenclature("Body")] public class BodyTarget : Orbitable, IKOSTargetable { - private static string DumpName = "name"; + private const string DumpName = "name"; public CelestialBody Body { get; set; } @@ -67,17 +67,6 @@ public static BodyTarget CreateOrGetExisting(CelestialBody body, SharedObjects s return newlyConstructed; } - // Required for all IDumpers for them to work, but can't enforced by the interface because it's static: - public static Orbitable CreateFromDump(SafeSharedObjects shared, Dump d) - { - var newObj = CreateOrGetExisting(BodyFromDump(d), (SharedObjects)shared); - // Uncomment the line below if LoadDump ever does more things in the future. - // Right now, LoadDump is redundant with CreateOrGetExisting's work. - // - // newObj.LoadDump(d); - return newObj; - } - public static void ClearInstanceCache() { if (instanceCache == null) @@ -274,16 +263,6 @@ public override ISuffixResult GetSuffix(string suffixName, bool failOkay) return base.GetSuffix(suffixName, failOkay); } - public override string ToString() - { - if (Body != null) - { - return "BODY(\"" + Body.name + "\")"; - } - - return base.ToString(); - } - public ITargetable Target { get { return Body; } @@ -322,32 +301,19 @@ public void SetSharedObjects(SharedObjects sharedObjects) Shared = sharedObjects; } - public override Dump Dump() + public override Dump Dump(DumperState s) { - var dump = new DumpWithHeader - { - Header = string.Format("BODY '{0}'", Body.bodyName) - }; + DumpDictionary dump = new DumpDictionary(this.GetType()); dump.Add(DumpName, Body.bodyName); return dump; } - public override void LoadDump(Dump dump) - { - Body = BodyFromDump(dump); - } - - private static CelestialBody BodyFromDump(Dump dump) + [DumpDeserializer] + public static BodyTarget CreateFromDump(DumpDictionary d, SafeSharedObjects shared) { - string name = dump[DumpName] as string; - - if (name == null) - { - throw new KOSSerializationException("Body's name is null or invalid"); - } - + string name = d.GetString(DumpName); CelestialBody body = VesselUtils.GetBodyByName(name); if (body == null) @@ -355,7 +321,18 @@ private static CelestialBody BodyFromDump(Dump dump) throw new KOSSerializationException("Body with the given name does not exist"); } - return body; + return CreateOrGetExisting(body, shared as SharedObjects); + } + + [DumpPrinter] + public static void Print(DumpDictionary dump, IndentedStringBuilder sb) + { + sb.Append("BODY(\""); + + string name = dump.GetString(DumpName); + sb.Append(name.Replace("\"", "\"\"")); + + sb.Append("\")"); } // The data that identifies a unique instance of this class, for use diff --git a/src/kOS/Suffixed/Direction.cs b/src/kOS/Suffixed/Direction.cs index eaa171105e..44c6b9b29c 100644 --- a/src/kOS/Suffixed/Direction.cs +++ b/src/kOS/Suffixed/Direction.cs @@ -9,12 +9,12 @@ namespace kOS.Suffixed { [Safe.Utilities.KOSNomenclature("Direction")] [Safe.Utilities.KOSNomenclature("Rotation", CSharpToKOS = false)] - public class Direction : SerializableStructure + public class Direction : Structure { - static string DumpQuaternionW = "q_w"; - static string DumpQuaternionX = "q_x"; - static string DumpQuaternionY = "q_y"; - static string DumpQuaternionZ = "q_z"; + const string DumpQuaternionW = "q_w"; + const string DumpQuaternionX = "q_x"; + const string DumpQuaternionY = "q_y"; + const string DumpQuaternionZ = "q_z"; private Vector3d euler; private Quaternion rotation; @@ -46,14 +46,6 @@ public Direction(Vector3d v3D, bool isEuler) } } - // Required for all IDumpers for them to work, but can't enforced by the interface because it's static: - public static Direction CreateFromDump(SafeSharedObjects shared, Dump d) - { - var newObj = new Direction(); - newObj.LoadDump(d); - return newObj; - } - // The following two are effectively constructors, but because they have // identical signatures they couldn't be differentiated if they were // constructors (which is probably why Unity didn't make them constructors @@ -241,31 +233,41 @@ public Direction RelativeFrom(Direction fromDir) return new Direction(Quaternion.RotateTowards(fromDir.rotation, rotation, 99999.0f)); } - public override string ToString() + public override Dump Dump(DumperState s) { - return "R(" + Math.Round(euler.x, 3) + "," + Math.Round(euler.y, 3) + "," + Math.Round(euler.z, 3) + ")"; + DumpDictionary dump = new DumpDictionary(this.GetType()); + + dump.Add(DumpQuaternionW, rotation.w); + dump.Add(DumpQuaternionX, rotation.x); + dump.Add(DumpQuaternionY, rotation.y); + dump.Add(DumpQuaternionZ, rotation.z); + + return dump; } - public override Dump Dump() + [DumpDeserializer] + public static Quaternion CreateFromDump(DumpDictionary d, SafeSharedObjects shared) { - DumpWithHeader dump = new DumpWithHeader - { - { DumpQuaternionW, rotation.w }, - { DumpQuaternionX, rotation.x }, - { DumpQuaternionY, rotation.y }, - { DumpQuaternionZ, rotation.z } - }; - return dump; + double w = d.GetDouble(DumpQuaternionW); + double x = d.GetDouble(DumpQuaternionX); + double y = d.GetDouble(DumpQuaternionY); + double z = d.GetDouble(DumpQuaternionZ); + + return new Quaternion((float)w, (float)x, (float)y, (float)z); } - public override void LoadDump(Dump dump) + [DumpPrinter] + public static void Print(DumpDictionary d, IndentedStringBuilder sb) { - Rotation = new Quaternion( - (float)Convert.ToDouble(dump[DumpQuaternionW]), - (float)Convert.ToDouble(dump[DumpQuaternionX]), - (float)Convert.ToDouble(dump[DumpQuaternionY]), - (float)Convert.ToDouble(dump[DumpQuaternionZ]) - ); + double w = d.GetDouble(DumpQuaternionW); + double x = d.GetDouble(DumpQuaternionX); + double y = d.GetDouble(DumpQuaternionY); + double z = d.GetDouble(DumpQuaternionZ); + + var q = new Quaternion((float)w, (float)x, (float)y, (float)z); + var euler = q.eulerAngles; + + sb.Append("R(" + Math.Round(euler.x, 3) + "," + Math.Round(euler.y, 3) + "," + Math.Round(euler.z, 3) + ")"); } } } \ No newline at end of file diff --git a/src/kOS/Suffixed/GeoCoordinates.cs b/src/kOS/Suffixed/GeoCoordinates.cs index c71d1f08f7..7c7289c76a 100644 --- a/src/kOS/Suffixed/GeoCoordinates.cs +++ b/src/kOS/Suffixed/GeoCoordinates.cs @@ -12,11 +12,11 @@ namespace kOS.Suffixed { [kOS.Safe.Utilities.KOSNomenclature("GeoCoordinates")] [kOS.Safe.Utilities.KOSNomenclature("LatLng", CSharpToKOS = false)] - public class GeoCoordinates : SerializableStructure + public class GeoCoordinates : Structure { - private static string DumpLat = "lat"; - private static string DumpLng = "lng"; - private static string DumpBody = "body"; + private const string DumpLat = "lat"; + private const string DumpLng = "lng"; + private const string DumpBody = "body"; private double lat; private double lng; @@ -47,14 +47,6 @@ private set private const int TERRAIN_MASK_BIT = 15; - // Only used by CreateFromDump() and the other constructors. - // Don't make it public because it leaves fields unpopulated if - // used by itself: - private GeoCoordinates() - { - GeoCoordsInitializeSuffixes(); - } - /// /// Build a GeoCoordinates from the current lat/long of the orbitable /// object passed in. The object being checked for should be in the same @@ -62,8 +54,9 @@ private GeoCoordinates() /// /// object to take current coords of /// to know the current CPU's running vessel - public GeoCoordinates(Orbitable orb, SharedObjects sharedObj) : this() + public GeoCoordinates(Orbitable orb, SharedObjects sharedObj) { + GeoCoordsInitializeSuffixes(); Shared = sharedObj; Vector p = orb.GetPosition(); Latitude = orb.PositionToLatitude(p); @@ -78,8 +71,9 @@ public GeoCoordinates(Orbitable orb, SharedObjects sharedObj) : this() /// to know the current CPU's running vessel /// latitude /// longitude - public GeoCoordinates(CelestialBody body, SharedObjects sharedObj, double latitude, double longitude) : this() + public GeoCoordinates(CelestialBody body, SharedObjects sharedObj, double latitude, double longitude) { + GeoCoordsInitializeSuffixes(); Latitude = latitude; Longitude = longitude; Shared = sharedObj; @@ -103,23 +97,15 @@ public GeoCoordinates(SharedObjects sharedObj, float latitude, float longitude) /// to know the current CPU's running vessel /// latitude /// longitude - public GeoCoordinates(SharedObjects sharedObj, double latitude, double longitude) : this() + public GeoCoordinates(SharedObjects sharedObj, double latitude, double longitude) { + GeoCoordsInitializeSuffixes(); Latitude = latitude; Longitude = longitude; Shared = sharedObj; Body = Shared.Vessel.GetOrbit().referenceBody; } - // Required for all IDumpers for them to work, but can't enforced by the interface because it's static: - public static GeoCoordinates CreateFromDump(SafeSharedObjects shared, Dump d) - { - var newObj = new GeoCoordinates(); - newObj.Shared = (SharedObjects)shared; - newObj.LoadDump(d); - return newObj; - } - /// /// The bearing from the current CPU vessel to the surface spot with the /// given lat/long coords, relative to the current CPU vessel's heading. @@ -337,33 +323,43 @@ private void GeoCoordsInitializeSuffixes() "This is the movement of that spot due to planetary rotation.")); } - public override string ToString() + public void SetSharedObjects(SharedObjects sharedObjects) { - return string.Format("{0}:GEOPOSITIONLATLNG({1},{2})", Body.GetName(), Latitude, Longitude); + Shared = sharedObjects; } - public void SetSharedObjects(SharedObjects sharedObjects) + + public override Dump Dump(DumperState s) { - Shared = sharedObjects; + DumpDictionary dump = new DumpDictionary(this.GetType()); + + dump.Add(DumpLat, lat); + dump.Add(DumpLng, lng); + using(var c = s.Context(this)) + dump.Add(DumpBody, BodyTarget.CreateOrGetExisting(Body, Shared), c); + + return dump; } - public override Dump Dump() + [DumpDeserializer] + public static GeoCoordinates CreateFromDump(DumpDictionary d, SafeSharedObjects shared) { - var dictionary = new DumpWithHeader - { - {DumpLat, lat}, - {DumpLng, lng}, - {DumpBody, BodyTarget.CreateOrGetExisting(Body, Shared)} - }; - return dictionary; + var body = (d.GetStructure(DumpBody, shared) as BodyTarget).Body; + double lat = d.GetDouble(DumpLat); + double lng = d.GetDouble(DumpLng); + + return new GeoCoordinates(body, shared as SharedObjects, lat, lng); } - public override void LoadDump(Dump dump) + [DumpPrinter] + public static void Print(DumpDictionary d, IndentedStringBuilder sb) { - Body = (dump[DumpBody] as BodyTarget).Body; - lat = Convert.ToDouble(dump[DumpLat]); - lng = Convert.ToDouble(dump[DumpLng]); + string name = (d.GetDump(DumpBody) as DumpDictionary)?.GetString("name") ?? "unknown"; + double lat = d.GetDouble(DumpLat); + double lng = d.GetDouble(DumpLng); + + sb.Append(string.Format("{0}:GEOPOSITIONLATLNG({1},{2})", name, lat, lng)); } } } diff --git a/src/kOS/Suffixed/HsvaColor.cs b/src/kOS/Suffixed/HsvaColor.cs index 4af0d60c8d..68a5d2eb16 100644 --- a/src/kOS/Suffixed/HsvaColor.cs +++ b/src/kOS/Suffixed/HsvaColor.cs @@ -10,21 +10,14 @@ namespace kOS.Suffixed [kOS.Safe.Utilities.KOSNomenclature("HSVA")] public class HsvaColor : RgbaColor { - private static string DumpH = "H"; - private static string DumpS = "S"; - private static string DumpV = "V"; - private static string DumpA = "A"; + private const string DumpH = "H"; + private const string DumpS = "S"; + private const string DumpV = "V"; + private const string DumpA = "A"; private float hue; private float saturation; private float hsvValue; - protected HsvaColor() - { - // InitAfterSettinFields() not called here, which is why this must - // not be made public. It's just bere because the IDumper - // system needs it for how CreateFromDump() works. - } - public HsvaColor(float hue, float saturation, float value, float alpha = 1.0f) { this.hue = hue; @@ -41,19 +34,6 @@ private void InitAfterSettingFields() ReconcileHsvToRgb(); } - // Required for all IDumpers for them to work, but can't enforced by the interface because it's static: - public static HsvaColor CreateFromDump(SafeSharedObjects shared, Dump d) - { - var newObj = new HsvaColor(); - newObj.LoadDump(d); - return newObj; - } - - public override string ToString() - { - return string.Format("HSVA({0}, {1}, {2}, {3})", hue, saturation, hsvValue, Alpha); - } - protected override void Recalculate() { base.Recalculate(); @@ -82,25 +62,39 @@ private void ReconcileRgbToHsv() saturation = newSaturation; hsvValue = newValue; } - public override Dump Dump() + + public override Dump Dump(DumperState s) { - DumpWithHeader dump = new DumpWithHeader - { - {DumpH, hue }, - {DumpS, saturation }, - {DumpV, hsvValue }, - {DumpA, Alpha } - }; + DumpDictionary dump = new DumpDictionary(this.GetType()); + + dump.Add(DumpH, hue); + dump.Add(DumpS, saturation); + dump.Add(DumpV, hsvValue); + dump.Add(DumpA, Alpha); + return dump; } - public override void LoadDump(Dump dump) + [DumpDeserializer] + public static new HsvaColor CreateFromDump(DumpDictionary d, SafeSharedObjects shared) { - hue = (float)Convert.ToDouble(dump[DumpH]); - saturation = (float)Convert.ToDouble(dump[DumpS]); - hsvValue = (float)Convert.ToDouble(dump[DumpV]); - Alpha = (float)Convert.ToDouble(dump[DumpA]); - InitAfterSettingFields(); + double h = d.GetDouble(DumpH); + double s = d.GetDouble(DumpS); + double v = d.GetDouble(DumpV); + double a = d.GetDouble(DumpA); + + return new HsvaColor((float)h, (float)s, (float)v, (float)a); + } + + [DumpPrinter] + public static new void Print(DumpDictionary d, IndentedStringBuilder sb) + { + double h = d.GetDouble(DumpH); + double s = d.GetDouble(DumpS); + double v = d.GetDouble(DumpV); + double a = d.GetDouble(DumpA); + + sb.Append(string.Format("HSVA({0}, {1}, {2}, {3})", h, s, v, a)); } } } \ No newline at end of file diff --git a/src/kOS/Suffixed/Orbitable.cs b/src/kOS/Suffixed/Orbitable.cs index f21301e25a..badc885f7b 100644 --- a/src/kOS/Suffixed/Orbitable.cs +++ b/src/kOS/Suffixed/Orbitable.cs @@ -13,7 +13,7 @@ namespace kOS.Suffixed /// or a moon. /// [kOS.Safe.Utilities.KOSNomenclature("Orbitable")] - public abstract class Orbitable : SerializableStructure + public abstract class Orbitable : Structure { protected Orbitable(SharedObjects shareObj) : this() { diff --git a/src/kOS/Suffixed/RgbaColor.cs b/src/kOS/Suffixed/RgbaColor.cs index be54dc00f7..dcc2b4a16e 100644 --- a/src/kOS/Suffixed/RgbaColor.cs +++ b/src/kOS/Suffixed/RgbaColor.cs @@ -9,12 +9,12 @@ namespace kOS.Suffixed { [kOS.Safe.Utilities.KOSNomenclature("RGBA")] - public class RgbaColor : SerializableStructure + public class RgbaColor : Structure { - static string DumpR = "R"; - static string DumpG = "G"; - static string DumpB = "B"; - static string DumpA = "A"; + const string DumpR = "R"; + const string DumpG = "G"; + const string DumpB = "B"; + const string DumpA = "A"; protected float Red { get; set; } @@ -48,14 +48,6 @@ public RgbaColor(RgbaColor copyFrom) Alpha = copyFrom.Alpha; } - // Required for all IDumpers for them to work, but can't enforced by the interface because it's static: - public static RgbaColor CreateFromDump(SafeSharedObjects shared, Dump d) - { - var newObj = new RgbaColor(); - newObj.LoadDump(d); - return newObj; - } - private void InitializeSuffixColor() { AddSuffix(new[] { "R", "RED" }, new ClampSetSuffix(() => Red, value => { Red = value; Recalculate(); }, 0, 255)); @@ -78,11 +70,6 @@ public Color Color } } - public override string ToString() - { - return string.Format("RGBA({0}, {1}, {2}, {3})", Red, Green, Blue, Alpha); - } - /// /// Returns a string representing the Hex color code "#rrggbb" format /// for the color. (i.e. RED is "#ff0000"). Note that this cannot represent @@ -101,25 +88,38 @@ public StringValue ToHexNotation() return string.Format("#{0:x2}{1:x2}{2:x2}", redByte, greenByte, blueByte); } - public override Dump Dump() + public override Dump Dump(DumperState s) { - DumpWithHeader dump = new DumpWithHeader - { - {DumpR, Red }, - {DumpG, Green }, - {DumpB, Blue }, - {DumpA, Alpha } - }; + DumpDictionary dump = new DumpDictionary(this.GetType()); + + dump.Add(DumpR, Red); + dump.Add(DumpG, Green); + dump.Add(DumpB, Blue); + dump.Add(DumpA, Alpha); return dump; } - public override void LoadDump(Dump dump) + [DumpDeserializer] + public static RgbaColor CreateFromDump(DumpDictionary d, SafeSharedObjects shared) + { + double r = d.GetDouble(DumpR); + double g = d.GetDouble(DumpG); + double b = d.GetDouble(DumpB); + double a = d.GetDouble(DumpA); + + return new RgbaColor((float)r, (float)g, (float)b, (float)a); + } + + [DumpPrinter] + public static void Print(DumpDictionary d, IndentedStringBuilder sb) { - Red = (float)Convert.ToDouble(dump[DumpR]); - Green = (float)Convert.ToDouble(dump[DumpG]); - Blue = (float)Convert.ToDouble(dump[DumpB]); - Alpha = (float)Convert.ToDouble(dump[DumpA]); + double r = d.GetDouble(DumpR); + double g = d.GetDouble(DumpG); + double b = d.GetDouble(DumpB); + double a = d.GetDouble(DumpA); + + sb.Append(string.Format("RGBA({0}, {1}, {2}, {3})", r, g, b, a)); } } } diff --git a/src/kOS/Suffixed/StageValues.cs b/src/kOS/Suffixed/StageValues.cs index 3f7f3c595a..2b1ea288fe 100644 --- a/src/kOS/Suffixed/StageValues.cs +++ b/src/kOS/Suffixed/StageValues.cs @@ -143,10 +143,5 @@ public void CreatePartSet() SafeHouse.Logger.Log("StageValues: nd={0}, parts={1}", nextDecoupler, partHash.Count); } - - public override string ToString() - { - return string.Format("{0} Stage", base.ToString()); - } } } diff --git a/src/kOS/Suffixed/TimeBase.cs b/src/kOS/Suffixed/TimeBase.cs index b0c52185fc..6d639ae767 100644 --- a/src/kOS/Suffixed/TimeBase.cs +++ b/src/kOS/Suffixed/TimeBase.cs @@ -24,13 +24,8 @@ namespace kOS.Suffixed /// or starting at 0. /// [kOS.Safe.Utilities.KOSNomenclature("TimeBase")] - public abstract class TimeBase : SerializableStructure - { - /// - /// For serializaation, how will it be named in the JSON output. - /// - public abstract string DumpName { get; } - + public abstract class TimeBase : Structure + { /// /// Override with either 0 or 1 for whether counting years and days starts counting at 0 or at 1. /// @@ -43,17 +38,11 @@ public abstract class TimeBase : SerializableStructure protected int SecondsPerYear { get { return KSPUtil.dateTimeFormatter.Year; } } protected int SecondsPerMinute { get { return KSPUtil.dateTimeFormatter.Minute; } } - // Only used by CreateFromDump() and the other constructors. - // Don't make it public because it leaves fields - // unpopulated: - protected TimeBase() - { - InitializeSuffixes(); - } - public TimeBase(double unixStyleTime) : this() + public TimeBase(double unixStyleTime) { seconds = unixStyleTime; + InitializeSuffixes(); } // Required for all IDumpers for them to work, but can't enforced by the interface because it's static: @@ -261,11 +250,6 @@ public override int GetHashCode() } */ - public override string ToString() - { - return string.Format("TIME({0:0})", seconds); - } - /* public override Dump Dump() { diff --git a/src/kOS/Suffixed/TimeStamp.cs b/src/kOS/Suffixed/TimeStamp.cs index a6f9f5629f..485f45dbe1 100644 --- a/src/kOS/Suffixed/TimeStamp.cs +++ b/src/kOS/Suffixed/TimeStamp.cs @@ -15,21 +15,13 @@ namespace kOS.Suffixed [kOS.Safe.Utilities.KOSNomenclature("TimeStamp")] public class TimeStamp : TimeBase, IComparable { - public override string DumpName { get { return "timestamp"; } } + public const string DumpName = "timestamp"; /// /// Override with either 0 or 1 for whether counting years and days starts counting at 0 or at 1. /// protected override double CountOffset { get { return 1.0; } } - // Only used by CreateFromDump() and the other constructors. - // Don't make it public because it leaves fields - // unpopulated: - private TimeStamp() - { - InitializeSuffixes(); - } - public TimeStamp(double unixStyleTime) : base(unixStyleTime) { } @@ -45,7 +37,7 @@ public TimeStamp(double unixStyleTime) : base(unixStyleTime) /// /// /// - public TimeStamp(double year, double day, double hour, double minute, double second) : this() + public TimeStamp(double year, double day, double hour, double minute, double second) : this(0) { seconds = (year - 1) * SecondsPerYear + @@ -55,13 +47,6 @@ public TimeStamp(double year, double day, double hour, double minute, double sec second; } - public static TimeStamp CreateFromDump(SafeSharedObjects shared, Dump d) - { - var newObj = new TimeStamp(); - newObj.LoadDump(d); - return newObj; - } - protected override void InitializeSuffixes() { AddSuffix("YEAR", new SetSuffix(CalculateYear, value => ChangeYear(value))); @@ -176,26 +161,31 @@ public override int GetHashCode() return !(a == b); } - public override string ToString() + public override Dump Dump(DumperState s) { - return string.Format("TIMESTAMP({0})", seconds); + DumpDictionary dump = new DumpDictionary(this.GetType()); + + dump.Add(DumpName, seconds); + + return dump; } - public override Dump Dump() + [DumpDeserializer] + public static TimeStamp CreateFromDump(DumpDictionary d, SafeSharedObjects shared) { - var dump = new Dump - { - {DumpName, seconds} - }; + double seconds = d.GetDouble(DumpName); - return dump; + return new TimeStamp(seconds); } - public override void LoadDump(Dump dump) + [DumpPrinter] + public static void Print(DumpDictionary d, IndentedStringBuilder sb) { - seconds = Convert.ToDouble(dump[DumpName]); + double seconds = d.GetDouble(DumpName); + + sb.Append(string.Format("TIMESTAMP({0})", seconds)); } - + public int CompareTo(TimeStamp other) { return seconds.CompareTo(other.seconds); diff --git a/src/kOS/Suffixed/Timespan.cs b/src/kOS/Suffixed/Timespan.cs index 8b15e3dd5a..9e5ac2689e 100644 --- a/src/kOS/Suffixed/Timespan.cs +++ b/src/kOS/Suffixed/Timespan.cs @@ -16,20 +16,13 @@ namespace kOS.Suffixed [kOS.Safe.Utilities.KOSNomenclature("TimeSpan")] public class TimeSpan : TimeBase, IComparable { - public override string DumpName { get { return "timespan"; } } + public const string DumpName = "timespan"; /// /// Override with either 0 or 1 for whether counting years and days starts counting at 0 or at 1. /// protected override double CountOffset { get { return 0.0; } } - // Only used by CreateFromDump() and the other constructors. - // Don't make it public because it leaves fields - // unpopulated: - private TimeSpan() - { - InitializeSuffixes(); - } public TimeSpan(double unixStyleTime) : base(unixStyleTime) { @@ -46,7 +39,7 @@ public TimeSpan(double unixStyleTime) : base(unixStyleTime) /// /// /// - public TimeSpan(double year, double day, double hour, double minute, double second) : this() + public TimeSpan(double year, double day, double hour, double minute, double second) : this(0) { seconds = year * SecondsPerYear + @@ -56,13 +49,6 @@ public TimeSpan(double year, double day, double hour, double minute, double seco second; } - public static TimeSpan CreateFromDump(SafeSharedObjects shared, Dump d) - { - var newObj = new TimeSpan(); - newObj.LoadDump(d); - return newObj; - } - protected override void InitializeSuffixes() { AddSuffix("YEAR", new SetSuffix(CalculateYear, value => ChangeYear(value))); @@ -173,26 +159,31 @@ public override int GetHashCode() return !(a == b); } - public override string ToString() + public override Dump Dump(DumperState s) { - return string.Format("TIMESPAN({0})", seconds); + DumpDictionary dump = new DumpDictionary(this.GetType()); + + dump.Add(DumpName, seconds); + + return dump; } - public override Dump Dump() + [DumpDeserializer] + public static TimeSpan CreateFromDump(DumpDictionary d, SafeSharedObjects shared) { - var dump = new Dump - { - {DumpName, seconds} - }; + double seconds = d.GetDouble(DumpName); - return dump; + return new TimeSpan(seconds); } - public override void LoadDump(Dump dump) + [DumpPrinter] + public static void Print(DumpDictionary d, IndentedStringBuilder sb) { - seconds = Convert.ToDouble(dump[DumpName]); + double seconds = d.GetDouble(DumpName); + + sb.Append(string.Format("TIMESPAN({0})", seconds)); } - + public int CompareTo(TimeSpan other) { return seconds.CompareTo(other.seconds); diff --git a/src/kOS/Suffixed/Vector.cs b/src/kOS/Suffixed/Vector.cs index 08bdf55d68..0a3c96c46c 100644 --- a/src/kOS/Suffixed/Vector.cs +++ b/src/kOS/Suffixed/Vector.cs @@ -10,7 +10,7 @@ namespace kOS.Suffixed { [kOS.Safe.Utilities.KOSNomenclature("Vector")] - public class Vector : SerializableStructure + public class Vector : Structure { public const string DumpX = "x"; public const string DumpY = "y"; @@ -52,14 +52,6 @@ public Vector(double x, double y, double z) : this() Z = z; } - // Required for all IDumpers for them to work, but can't enforced by the interface because it's static: - public static Vector CreateFromDump(SafeSharedObjects shared, Dump d) - { - var newObj = new Vector(); - newObj.LoadDump(d); - return newObj; - } - private void InitializeSuffixes() { AddSuffix("X", new SetSuffix(() => X, value => X = value)); @@ -120,11 +112,6 @@ public Vector3 ToVector3() // Vector3 is the single-precision version of Vector3 return new Vector3((float)X, (float)Y, (float)Z); } - public override string ToString() - { - return "V(" + X + ", " + Y + ", " + Z + ")"; - } - public override bool Equals(object obj) { Type compareType = typeof(Vector); @@ -234,9 +221,9 @@ public static explicit operator Direction(Vector d) return !(a == b); } - public override Dump Dump() + public override Dump Dump(DumperState s) { - DumpWithHeader dump = new DumpWithHeader(); + DumpDictionary dump = new DumpDictionary(this.GetType()); dump.Add(DumpX, X); dump.Add(DumpY, Y); @@ -245,11 +232,30 @@ public override Dump Dump() return dump; } - public override void LoadDump(Dump dump) + [DumpDeserializer] + public static Vector CreateFromDump(DumpDictionary d, SafeSharedObjects shared) { - X = Convert.ToDouble(dump[DumpX]); - Y = Convert.ToDouble(dump[DumpY]); - Z = Convert.ToDouble(dump[DumpZ]); + double x = d.GetDouble(DumpX); + double y = d.GetDouble(DumpY); + double z = d.GetDouble(DumpZ); + + return new Vector(x, y, z); + } + + [DumpPrinter] + public static void Print(DumpDictionary d, IndentedStringBuilder sb) + { + double x = d.GetDouble(DumpX); + double y = d.GetDouble(DumpY); + double z = d.GetDouble(DumpZ); + + sb.Append("V("); + sb.Append(x.ToString()); + sb.Append(", "); + sb.Append(y.ToString()); + sb.Append(", "); + sb.Append(z.ToString()); + sb.Append(")"); } } } \ No newline at end of file diff --git a/src/kOS/Suffixed/VesselTarget.Hooks.cs b/src/kOS/Suffixed/VesselTarget.Hooks.cs index bc91a52511..5e655c95ac 100644 --- a/src/kOS/Suffixed/VesselTarget.Hooks.cs +++ b/src/kOS/Suffixed/VesselTarget.Hooks.cs @@ -130,17 +130,6 @@ public static VesselTarget CreateOrGetExisting(Vessel target, SharedObjects shar return newlyConstructed; } - // Required for all IDumpers for them to work, but can't enforced by the interface because it's static: - public static VesselTarget CreateFromDump(SafeSharedObjects shared, Dump d) - { - var newObj = CreateOrGetExisting(VesselFromDump(d), (SharedObjects)shared); - // Uncomment the line below if LoadDump ever does more things in the future. - // Right now, LoadDump is redundant with CreateOrGetExisting's work. - // - // newObj.LoadDump(d); - return newObj; - } - /// /// Dispose all VeselTarget instances - called by SceneChangeCleaner /// diff --git a/src/kOS/Suffixed/VesselTarget.cs b/src/kOS/Suffixed/VesselTarget.cs index 3e0929a58c..6b59759237 100644 --- a/src/kOS/Suffixed/VesselTarget.cs +++ b/src/kOS/Suffixed/VesselTarget.cs @@ -23,7 +23,7 @@ namespace kOS.Suffixed [kOS.Safe.Utilities.KOSNomenclature("Vessel")] public partial class VesselTarget : Orbitable, IKOSTargetable, IDisposable { - private static string DumpGuid = "guid"; + private const string DumpGuid = "guid"; public Guid Guid { get { return Vessel.id; } } public override Orbit Orbit { get { return Vessel.orbit; } } @@ -236,11 +236,6 @@ static VesselTarget() }; } - public override string ToString() - { - return "VESSEL(\"" + Vessel.vesselName + "\")"; - } - private void InitializeSuffixes() { AddSuffix("PARTSNAMED", new OneArgsSuffix(GetPartsNamed)); @@ -563,39 +558,50 @@ public override int GetHashCode() return !Equals(left, right); } - public override Dump Dump() + public override Dump Dump(DumperState s) { - DumpWithHeader dump = new DumpWithHeader(); + DumpDictionary dump = new DumpDictionary(this.GetType()); - dump.Header = "VESSEL '" + Vessel.vesselName + "'"; dump.Add(DumpGuid, Vessel.id.ToString()); return dump; } - public override void LoadDump(Dump dump) + [DumpDeserializer] + public static VesselTarget CreateFromDump(DumpDictionary d, SafeSharedObjects shared) { - Vessel = VesselFromDump(dump); - } - - private static Vessel VesselFromDump(Dump dump) - { - string guid = dump[DumpGuid] as string; + string guid = d.GetString(DumpGuid); + Vessel vessel = FlightGlobals.Vessels.Find((v) => v.id.ToString().Equals(guid)); - if (guid == null) + if (vessel == null) { - throw new KOSSerializationException("Vessel's guid is null or invalid"); + throw new KOSSerializationException("Vessel with the given id does not exist"); } + return CreateOrGetExisting(vessel, shared as SharedObjects); + } + + [DumpPrinter] + public static void Print(DumpDictionary dump, IndentedStringBuilder sb) + { + sb.Append("VESSEL("); + + string guid = dump.GetString(DumpGuid); Vessel vessel = FlightGlobals.Vessels.Find((v) => v.id.ToString().Equals(guid)); - if (vessel == null) + if (vessel) { - throw new KOSSerializationException("Vessel with the given id does not exist"); + sb.Append("\""); + sb.Append(vessel.vesselName.Replace("\"", "\"\"")); + sb.Append("\""); + } else + { + sb.Append("<"); + sb.Append(guid); + sb.Append(">"); } - - return vessel; + sb.Append(")"); } } } diff --git a/src/kOS/kOS.csproj b/src/kOS/kOS.csproj index 336b73c56e..c15015b9f7 100644 --- a/src/kOS/kOS.csproj +++ b/src/kOS/kOS.csproj @@ -281,7 +281,6 @@ -