using System; using System.Collections.Generic; using System.IO; using System.Reflection; using Vellum; namespace Ariadne.Vellum { /// /// Saves every arc class as a value, so an arc can be a field of any saved object. /// /// /// This is a converter family, so a game needs no converter of its own for an arc. /// /// /// A concrete class with the constructor Ariadne.Generator writes to load it. /// /// Whatever each tick hands the arc. public class ArcConverter : SaveConverter where ArcType : Arc { private readonly ConstructorInfo loader; private readonly ArcPartList parts = new ArcPartList(); private readonly bool listsItsParts; private const string ListPartsName = "ListParts"; /// /// The class has no constructor to load it from a save. /// public ArcConverter() { loader = ArcLoader.FindConstructor(typeof(ArcType)); listsItsParts = ArcLoader.TryListParts(typeof(ArcType), parts); } // A malformed table is left to Read, which reports it. private static bool TryGetTable(SaveNode node, string key, out SaveNode table) { if (!node.Entries.TryGetValue(key, out table)) { return true; } return table.Kind == SaveNode.Kinds.Table; } public override bool TryListChildren(SaveNode node, ChildList children) { return TryListChildren(node, children, converters: null); } public override bool TryListChildren(SaveNode node, ChildList children, SaveConverters converters) { if (!listsItsParts || node.Kind != SaveNode.Kinds.Table) { return false; } SaveNode fields; SaveNode locals; if (!TryGetTable(node, ArcRecord.FieldsKey, out fields) || !TryGetTable(node, ArcRecord.LocalsKey, out locals)) { return false; } AddChildren(parts, fields, locals, children); var pending = new Stack>(); if (!PushScopes(parts, locals, pending, converters)) { return false; } // A call's locals are a table inside its caller's, which the list's scopes mirror a level at a time. while (pending.Count > 0) { KeyValuePair level = pending.Pop(); AddChildren(level.Key, fields: null, locals: level.Value, children: children); if (!PushScopes(level.Key, level.Value, pending, converters)) { return false; } } return true; } private static void AddChildren(ArcPartList list, SaveNode fields, SaveNode locals, ChildList children) { for (int i = 0; i < list.Count; ++i) { SaveNode table = fields; if (list.IsLocal[i]) { table = locals; } SaveNode child; if (table != null && table.Entries.TryGetValue(list.Keys[i], out child)) { children.Add(child, list.Types[i]); } } } // The save names the types a polymorphic call's frame is built from, so its locals are listed by the closed // frame class. Returns null if the save's names can't be used, which is left to Read to report. private static ArcPartList ListPolymorphic(Type definition, SaveNode scope, SaveConverters converters) { SaveNode names; if (converters == null || !scope.Entries.TryGetValue(ArcRecord.TypesKey, out names) || names.Kind != SaveNode.Kinds.List) { return null; } IReadOnlyList items = names.RequireItems(); var arguments = new Type[items.Count]; try { for (int i = 0; i < arguments.Length; ++i) { arguments[i] = converters.ResolveName(items[i].RequireText()); } Type closed = definition.MakeGenericType(arguments); var returnMe = new ArcPartList(); closed.GetMethod(ListPartsName, BindingFlags.Public | BindingFlags.Static) .Invoke(null, new object[] { returnMe }); return returnMe; } catch (Exception exception) when (exception is ArgumentException || exception is InvalidDataException) { return null; } } // Returns false if a call's entry isn't a table, or a polymorphic call's types can't be used. Read reports // either. private static bool PushScopes(ArcPartList list, SaveNode locals, Stack> pending, SaveConverters converters) { for (int i = 0; i < list.Scopes.Count; ++i) { SaveNode scope; if (locals != null && locals.Entries.TryGetValue(list.ScopeNames[i], out scope)) { if (scope.Kind != SaveNode.Kinds.Table) { return false; } pending.Push(new KeyValuePair(list.Scopes[i], scope)); } } for (int i = 0; i < list.PolymorphicNames.Count; ++i) { SaveNode scope; if (locals != null && locals.Entries.TryGetValue(list.PolymorphicNames[i], out scope)) { if (scope.Kind != SaveNode.Kinds.Table) { return false; } ArcPartList listed = ListPolymorphic(list.PolymorphicDefinitions[i], scope, converters); if (listed == null) { return false; } pending.Push(new KeyValuePair(listed, scope)); } } return true; } public override SaveNode Write(ArcType value, SaveSession session) { var record = new ArcRecord(session); value.Save(record); return record.ToNode(); } /// /// The node isn't one Write could have made, or nests calls deeper than the stack can load. /// /// The save's locals don't match the script's. public override ArcType Read(SaveNode node, SaveSession session) { ArcRecord record = ArcRecord.FromNode(node, session); // Loading follows nested calls down the stack, as running does, so a save past the limit was edited or was // made on a bigger stack. try { var returnMe = (ArcType)ArcLoader.Create(loader, record); returnMe.VerifyLoaded(record); return returnMe; } catch (InsufficientExecutionStackException exception) { throw new InvalidDataException("The arc's calls are nested deeper than the stack can load.", exception); } } } }