Files
2021-06-30 21:39:19 +10:00

207 lines
8.2 KiB
C#

using System.Collections.Generic;
namespace Ink.Parsed
{
[System.Flags]
public enum SequenceType
{
Stopping = 1, // default
Cycle = 2,
Shuffle = 4,
Once = 8
}
public class Sequence : Parsed.Object
{
public List<Parsed.Object> sequenceElements;
public SequenceType sequenceType;
public Sequence (List<ContentList> elementContentLists, SequenceType sequenceType)
{
this.sequenceType = sequenceType;
this.sequenceElements = new List<Parsed.Object> ();
foreach (var elementContentList in elementContentLists) {
var contentObjs = elementContentList.content;
Parsed.Object seqElObject = null;
// Don't attempt to create a weave for the sequence element
// if the content list is empty. Weaves don't like it!
if (contentObjs == null || contentObjs.Count == 0)
seqElObject = elementContentList;
else
seqElObject = new Weave (contentObjs);
this.sequenceElements.Add (seqElObject);
AddContent (seqElObject);
}
}
// Generate runtime code that looks like:
//
// chosenIndex = MIN(sequence counter, num elements) e.g. for "Stopping"
// if chosenIndex == 0, divert to s0
// if chosenIndex == 1, divert to s1 [etc]
//
// - s0:
// <content for sequence element>
// divert to no-op
// - s1:
// <content for sequence element>
// divert to no-op
// - s2:
// empty branch if using "once"
// divert to no-op
//
// no-op
//
public override Runtime.Object GenerateRuntimeObject ()
{
var container = new Runtime.Container ();
container.visitsShouldBeCounted = true;
container.countingAtStartOnly = true;
_sequenceDivertsToResove = new List<SequenceDivertToResolve> ();
// Get sequence read count
container.AddContent (Runtime.ControlCommand.EvalStart ());
container.AddContent (Runtime.ControlCommand.VisitIndex ());
bool once = (sequenceType & SequenceType.Once) > 0;
bool cycle = (sequenceType & SequenceType.Cycle) > 0;
bool stopping = (sequenceType & SequenceType.Stopping) > 0;
bool shuffle = (sequenceType & SequenceType.Shuffle) > 0;
var seqBranchCount = sequenceElements.Count;
if (once) seqBranchCount++;
// Chosen sequence index:
// - Stopping: take the MIN(read count, num elements - 1)
// - Once: take the MIN(read count, num elements)
// (the last one being empty)
if (stopping || once) {
//var limit = stopping ? seqBranchCount-1 : seqBranchCount;
container.AddContent (new Runtime.IntValue (seqBranchCount-1));
container.AddContent (Runtime.NativeFunctionCall.CallWithName ("MIN"));
}
// - Cycle: take (read count % num elements)
else if (cycle) {
container.AddContent (new Runtime.IntValue (sequenceElements.Count));
container.AddContent (Runtime.NativeFunctionCall.CallWithName ("%"));
}
// Shuffle
if (shuffle) {
// Create point to return to when sequence is complete
var postShuffleNoOp = Runtime.ControlCommand.NoOp();
// When visitIndex == lastIdx, we skip the shuffle
if ( once || stopping )
{
// if( visitIndex == lastIdx ) -> skipShuffle
int lastIdx = stopping ? sequenceElements.Count - 1 : sequenceElements.Count;
container.AddContent(Runtime.ControlCommand.Duplicate());
container.AddContent(new Runtime.IntValue(lastIdx));
container.AddContent(Runtime.NativeFunctionCall.CallWithName("=="));
var skipShuffleDivert = new Runtime.Divert();
skipShuffleDivert.isConditional = true;
container.AddContent(skipShuffleDivert);
AddDivertToResolve(skipShuffleDivert, postShuffleNoOp);
}
// This one's a bit more complex! Choose the index at runtime.
var elementCountToShuffle = sequenceElements.Count;
if (stopping) elementCountToShuffle--;
container.AddContent (new Runtime.IntValue (elementCountToShuffle));
container.AddContent (Runtime.ControlCommand.SequenceShuffleIndex ());
if (once || stopping) container.AddContent(postShuffleNoOp);
}
container.AddContent (Runtime.ControlCommand.EvalEnd ());
// Create point to return to when sequence is complete
var postSequenceNoOp = Runtime.ControlCommand.NoOp();
// Each of the main sequence branches, and one extra empty branch if
// we have a "once" sequence.
for (var elIndex=0; elIndex<seqBranchCount; elIndex++) {
// This sequence element:
// if( chosenIndex == this index ) divert to this sequence element
// duplicate chosen sequence index, since it'll be consumed by "=="
container.AddContent (Runtime.ControlCommand.EvalStart ());
container.AddContent (Runtime.ControlCommand.Duplicate ());
container.AddContent (new Runtime.IntValue (elIndex));
container.AddContent (Runtime.NativeFunctionCall.CallWithName ("=="));
container.AddContent (Runtime.ControlCommand.EvalEnd ());
// Divert branch for this sequence element
var sequenceDivert = new Runtime.Divert ();
sequenceDivert.isConditional = true;
container.AddContent (sequenceDivert);
Runtime.Container contentContainerForSequenceBranch;
// Generate content for this sequence element
if ( elIndex < sequenceElements.Count ) {
var el = sequenceElements[elIndex];
contentContainerForSequenceBranch = (Runtime.Container)el.runtimeObject;
}
// Final empty branch for "once" sequences
else {
contentContainerForSequenceBranch = new Runtime.Container();
}
contentContainerForSequenceBranch.name = "s" + elIndex;
contentContainerForSequenceBranch.InsertContent(Runtime.ControlCommand.PopEvaluatedValue(), 0);
// When sequence element is complete, divert back to end of sequence
var seqBranchCompleteDivert = new Runtime.Divert ();
contentContainerForSequenceBranch.AddContent (seqBranchCompleteDivert);
container.AddToNamedContentOnly (contentContainerForSequenceBranch);
// Save the diverts for reference resolution later (in ResolveReferences)
AddDivertToResolve (sequenceDivert, contentContainerForSequenceBranch);
AddDivertToResolve (seqBranchCompleteDivert, postSequenceNoOp);
}
container.AddContent (postSequenceNoOp);
return container;
}
void AddDivertToResolve(Runtime.Divert divert, Runtime.Object targetContent)
{
_sequenceDivertsToResove.Add( new SequenceDivertToResolve() {
divert = divert,
targetContent = targetContent
});
}
public override void ResolveReferences(Story context)
{
base.ResolveReferences (context);
foreach (var toResolve in _sequenceDivertsToResove) {
toResolve.divert.targetPath = toResolve.targetContent.path;
}
}
class SequenceDivertToResolve
{
public Runtime.Divert divert;
public Runtime.Object targetContent;
}
List<SequenceDivertToResolve> _sequenceDivertsToResove;
}
}