using System; using System.Collections.Generic; using System.IO; using Vellum; namespace Ariadne.Vellum { /// /// Saves a wait on triggers or conditions, which a script keeps as a local while it is paused. /// /// /// The wait and what it waits on refer to one another, so the wait is made empty first and filled in later. A wait /// on conditions lists them under a key of their own, which is how a load knows what kind of wait it is. /// public class TriggerWaitConverter : SaveConverter { private const string CapacityKey = "capacity"; private const string WaitKey = "wait"; private const string RequiresAllKey = "requiresAll"; private const string SeenKey = "seen"; private const string FiredKey = "fired"; private const string TriggersKey = "triggers"; private const string ConditionsKey = "conditions"; private const string LatchKey = "latch"; private const string InvertedKey = "inverted"; // A condition is saved as its latch and whether it is the latch's inverse, since an inverse has no data. private static SaveNode WriteConditions(TriggerWait value, SaveSession session) { SaveNode returnMe = SaveNode.NewList(value.Count); for (int i = 0; i < value.Count; ++i) { Condition condition = value.Conditions[i]; var inverse = condition as InvertedLatch; var latch = condition as Latch; if (inverse == null && latch == null) { throw new InvalidOperationException("Only a latch or its inverse can be saved as a condition a " + "wait waits on, and this is a " + condition.GetType().Name + "."); } SaveNode entry = SaveNode.NewTable(); entry.Entries[LatchKey] = session.ToNode(inverse == null ? latch : inverse.Original); entry.Entries[InvertedKey] = session.ToNode(inverse != null); returnMe.Items.Add(entry); } return returnMe; } private static void FillConditions(TriggerWait blank, IReadOnlyList items, SaveSession session) { for (int i = 0; i < items.Count; ++i) { var latch = session.FromNode(items[i].RequireEntry(LatchKey)); bool inverted = session.FromNode(items[i].RequireEntry(InvertedKey)); if (inverted) { blank.Conditions[i] = latch.Inverse; } else { blank.Conditions[i] = latch; } } } public override SaveNode Write(TriggerWait value, SaveSession session) { SaveNode returnMe = SaveNode.NewTable(); returnMe.Entries[CapacityKey] = session.ToNode(value.Subscriptions.Length); returnMe.Entries[WaitKey] = session.ToNode(value.Wait); returnMe.Entries[RequiresAllKey] = session.ToNode(value.RequiresAll); returnMe.Entries[SeenKey] = session.ToNode(value.Seen); returnMe.Entries[FiredKey] = session.ToNode(value.Fired); if (value.OnConditions) { returnMe.Entries[ConditionsKey] = WriteConditions(value, session); return returnMe; } SaveNode triggers = SaveNode.NewList(value.Count); for (int i = 0; i < value.Count; ++i) { triggers.Items.Add(session.ToNode(value.Triggers[i])); } returnMe.Entries[TriggersKey] = triggers; return returnMe; } public override TriggerWait Allocate(SaveNode node, SaveSession session) { int capacity = session.FromNode(node.RequireEntry(CapacityKey)); if (capacity < 1 || capacity > TriggerWait.MaximumTriggers) { throw new InvalidDataException("A saved wait holds " + capacity + " triggers, and a wait holds " + "between 1 and " + TriggerWait.MaximumTriggers + "."); } SaveNode ignored; return new TriggerWait(capacity, onConditions: node.TryGetEntry(ConditionsKey, out ignored)); } /// The saved wait is one no script could have paused in. public override void Fill(TriggerWait blank, SaveNode node, SaveSession session) { IReadOnlyList items = node.RequireEntry(blank.OnConditions ? ConditionsKey : TriggersKey) .RequireItems(); if (items.Count > blank.Subscriptions.Length) { throw new InvalidDataException("A saved wait of " + blank.Subscriptions.Length + " triggers lists " + items.Count + "."); } int seen = session.FromNode(node.RequireEntry(SeenKey)); int fired = session.FromNode(node.RequireEntry(FiredKey)); if (fired < -1 || fired >= items.Count) { throw new InvalidDataException("A saved wait of " + items.Count + " triggers was ended by trigger " + fired + "."); } if (items.Count < TriggerWait.MaximumTriggers && (unchecked((uint)seen) >> items.Count) != 0) { throw new InvalidDataException("A saved wait of " + items.Count + " triggers has raised " + seen + "."); } if (blank.OnConditions) { FillConditions(blank, items, session); } else { for (int i = 0; i < items.Count; ++i) { blank.Triggers[i] = session.FromNode(items[i]); } } blank.Count = items.Count; blank.Wait = session.FromNode(node.RequireEntry(WaitKey)); blank.RequiresAll = session.FromNode(node.RequireEntry(RequiresAllKey)); blank.Seen = seen; blank.Fired = fired; // A wait on conditions records nothing about what happened, so there is nothing to disagree with. if (!blank.OnConditions && blank.Count > 0 && blank.Complete != (fired >= 0)) { throw new InvalidDataException("A saved wait that is " + (blank.Complete ? "over" : "going on") + " says trigger " + fired + " ended it."); } } public override void Complete(TriggerWait blank, SaveNode node, SaveSession session) { } public override bool TryListChildren(SaveNode node, ChildList children) { SaveNode conditions; if (node.TryGetEntry(ConditionsKey, out conditions)) { IReadOnlyList entries; if (!conditions.TryGetItems(out entries)) { return false; } foreach (SaveNode entry in entries) { SaveNode latch; if (!entry.TryGetEntry(LatchKey, out latch)) { return false; } children.Add(latch, typeof(Latch)); } return true; } SaveNode triggers; return node.TryGetEntry(TriggersKey, out triggers) && SavedItems.ListChildren(triggers, typeof(Trigger), children); } public override TriggerWait Read(SaveNode node, SaveSession session) { TriggerWait returnMe = Allocate(node, session); session.Publish(node, returnMe); Fill(returnMe, node, session); return returnMe; } } }