using System; using System.Collections.Generic; using System.IO; using Vellum; namespace Amphora.Vellum { /// /// Saves a BinaryDecisionDiagram as the table of its decision nodes, in position order, and the position it starts /// at. /// /// /// The outcomes are positions 0 (never) and 1 (always), so the list starts at position 2. /// Loading rebuilds through a builder, so a hand-edited save that isn't reduced loads as the reduced diagram. /// public sealed class BinaryDecisionDiagramConverter : SaveConverter { public const string NodesKey = "nodes"; public const string RootKey = "root"; public const string VariableKey = "variable"; public const string IfFalseKey = "ifFalse"; public const string IfTrueKey = "ifTrue"; // Both outcomes come before the first decision node. private const int FirstDecisionPosition = 2; public override SaveNode Write(BinaryDecisionDiagram value, SaveSession session) { SaveNode nodes = SaveNode.NewList(); for (int position = FirstDecisionPosition; position < value.NodeCount + FirstDecisionPosition; ++position) { SaveNode node = SaveNode.NewTable(); node.Entries[VariableKey] = session.ToNode(value.VariableAt(position)); node.Entries[IfFalseKey] = session.ToNode(value.IfFalseAt(position)); node.Entries[IfTrueKey] = session.ToNode(value.IfTrueAt(position)); nodes.Items.Add(node); } SaveNode returnMe = SaveNode.NewTable(); returnMe.Entries[NodesKey] = nodes; returnMe.Entries[RootKey] = session.ToNode(value.RootPosition); return returnMe; } private static int ReadPosition(SaveReader reader, string key, int limit) { int position = reader.Require(key); if (position < 0 || position >= limit) { throw new InvalidDataException("A saved decision diagram has the position " + position + ", which is " + "not one of the " + limit + " before it."); } return position; } public override BinaryDecisionDiagram Read(SaveNode node, SaveSession session) { IReadOnlyList saved = node.RequireEntry(NodesKey).RequireItems(); var builder = new BinaryDecisionDiagramBuilder(); // Where each saved position ended up in the builder, which merges equal nodes. var positions = new int[saved.Count + FirstDecisionPosition]; positions[BinaryDecisionDiagramBuilder.NeverPosition] = BinaryDecisionDiagramBuilder.NeverPosition; positions[BinaryDecisionDiagramBuilder.AlwaysPosition] = BinaryDecisionDiagramBuilder.AlwaysPosition; for (int i = 0; i < saved.Count; ++i) { int own = i + FirstDecisionPosition; var decision = new SaveReader(saved[i], session); int variable = decision.Require(VariableKey); int ifFalse = ReadPosition(decision, IfFalseKey, own); int ifTrue = ReadPosition(decision, IfTrueKey, own); try { positions[own] = builder.Make(variable, positions[ifFalse], positions[ifTrue]); } catch (ArgumentException exception) { throw new InvalidDataException("A saved decision diagram has a node that asks about the variable " + variable + " before the nodes it goes to.", exception); } } var rootReader = new SaveReader(node, session); rootReader.Skip(NodesKey); int root = ReadPosition(rootReader, RootKey, positions.Length); return new BinaryDecisionDiagram(builder, positions[root]); } public override bool TryListChildren(SaveNode node, ChildList children) { // Everything it reads is built-in, and so left out. SaveNode nodes; return node.TryGetEntry(NodesKey, out nodes); } } }