- using System.Collections.Generic;
- using System.Linq;
- using System.Diagnostics;
- namespace Ink.Runtime
- {
- public class CallStack
- {
- public class Element
- {
- public Pointer currentPointer;
- public bool inExpressionEvaluation;
- public Dictionary<string, Runtime.Object> temporaryVariables;
- public PushPopType type;
-
-
-
- public int evaluationStackHeightWhenPushed;
-
-
- public int functionStartInOuputStream;
- public Element(PushPopType type, Pointer pointer, bool inExpressionEvaluation = false) {
- this.currentPointer = pointer;
- this.inExpressionEvaluation = inExpressionEvaluation;
- this.temporaryVariables = new Dictionary<string, Object>();
- this.type = type;
- }
- public Element Copy()
- {
- var copy = new Element (this.type, currentPointer, this.inExpressionEvaluation);
- copy.temporaryVariables = new Dictionary<string,Object>(this.temporaryVariables);
- copy.evaluationStackHeightWhenPushed = evaluationStackHeightWhenPushed;
- copy.functionStartInOuputStream = functionStartInOuputStream;
- return copy;
- }
- }
- public class Thread
- {
- public List<Element> callstack;
- public int threadIndex;
- public Pointer previousPointer;
- public Thread() {
- callstack = new List<Element>();
- }
- public Thread(Dictionary<string, object> jThreadObj, Story storyContext) : this() {
- threadIndex = (int) jThreadObj ["threadIndex"];
- List<object> jThreadCallstack = (List<object>) jThreadObj ["callstack"];
- foreach (object jElTok in jThreadCallstack) {
- var jElementObj = (Dictionary<string, object>)jElTok;
- PushPopType pushPopType = (PushPopType)(int)jElementObj ["type"];
- Pointer pointer = Pointer.Null;
- string currentContainerPathStr = null;
- object currentContainerPathStrToken;
- if (jElementObj.TryGetValue ("cPath", out currentContainerPathStrToken)) {
- currentContainerPathStr = currentContainerPathStrToken.ToString ();
- var threadPointerResult = storyContext.ContentAtPath (new Path (currentContainerPathStr));
- pointer.container = threadPointerResult.container;
- pointer.index = (int)jElementObj ["idx"];
- if (threadPointerResult.obj == null)
- throw new System.Exception ("When loading state, internal story location couldn't be found: " + currentContainerPathStr + ". Has the story changed since this save data was created?");
- else if (threadPointerResult.approximate)
- storyContext.Warning ("When loading state, exact internal story location couldn't be found: '" + currentContainerPathStr + "', so it was approximated to '"+pointer.container.path.ToString()+"' to recover. Has the story changed since this save data was created?");
- }
- bool inExpressionEvaluation = (bool)jElementObj ["exp"];
- var el = new Element (pushPopType, pointer, inExpressionEvaluation);
- object temps;
- if ( jElementObj.TryGetValue("temp", out temps) ) {
- el.temporaryVariables = Json.JObjectToDictionaryRuntimeObjs((Dictionary<string, object>)temps);
- } else {
- el.temporaryVariables.Clear();
- }
- callstack.Add (el);
- }
- object prevContentObjPath;
- if( jThreadObj.TryGetValue("previousContentObject", out prevContentObjPath) ) {
- var prevPath = new Path((string)prevContentObjPath);
- previousPointer = storyContext.PointerAtPath(prevPath);
- }
- }
- public Thread Copy() {
- var copy = new Thread ();
- copy.threadIndex = threadIndex;
- foreach(var e in callstack) {
- copy.callstack.Add(e.Copy());
- }
- copy.previousPointer = previousPointer;
- return copy;
- }
- public void WriteJson(SimpleJson.Writer writer)
- {
- writer.WriteObjectStart();
-
- writer.WritePropertyStart("callstack");
- writer.WriteArrayStart();
- foreach (CallStack.Element el in callstack)
- {
- writer.WriteObjectStart();
- if(!el.currentPointer.isNull) {
- writer.WriteProperty("cPath", el.currentPointer.container.path.componentsString);
- writer.WriteProperty("idx", el.currentPointer.index);
- }
- writer.WriteProperty("exp", el.inExpressionEvaluation);
- writer.WriteProperty("type", (int)el.type);
- if(el.temporaryVariables.Count > 0) {
- writer.WritePropertyStart("temp");
- Json.WriteDictionaryRuntimeObjs(writer, el.temporaryVariables);
- writer.WritePropertyEnd();
- }
- writer.WriteObjectEnd();
- }
- writer.WriteArrayEnd();
- writer.WritePropertyEnd();
-
- writer.WriteProperty("threadIndex", threadIndex);
- if (!previousPointer.isNull)
- {
- writer.WriteProperty("previousContentObject", previousPointer.Resolve().path.ToString());
- }
- writer.WriteObjectEnd();
- }
- }
- public List<Element> elements {
- get {
- return callStack;
- }
- }
- public int depth {
- get {
- return elements.Count;
- }
- }
- public Element currentElement {
- get {
- var thread = _threads [_threads.Count - 1];
- var cs = thread.callstack;
- return cs [cs.Count - 1];
- }
- }
- public int currentElementIndex {
- get {
- return callStack.Count - 1;
- }
- }
- public Thread currentThread
- {
- get {
- return _threads [_threads.Count - 1];
- }
- set {
- Debug.Assert (_threads.Count == 1, "Shouldn't be directly setting the current thread when we have a stack of them");
- _threads.Clear ();
- _threads.Add (value);
- }
- }
- public bool canPop {
- get {
- return callStack.Count > 1;
- }
- }
- public CallStack (Story storyContext)
- {
- _startOfRoot = Pointer.StartOf(storyContext.rootContentContainer);
- Reset();
- }
- public CallStack(CallStack toCopy)
- {
- _threads = new List<Thread> ();
- foreach (var otherThread in toCopy._threads) {
- _threads.Add (otherThread.Copy ());
- }
- _threadCounter = toCopy._threadCounter;
- _startOfRoot = toCopy._startOfRoot;
- }
- public void Reset()
- {
- _threads = new List<Thread>();
- _threads.Add(new Thread());
- _threads[0].callstack.Add(new Element(PushPopType.Tunnel, _startOfRoot));
- }
-
-
-
- public void SetJsonToken(Dictionary<string, object> jObject, Story storyContext)
- {
- _threads.Clear ();
- var jThreads = (List<object>) jObject ["threads"];
- foreach (object jThreadTok in jThreads) {
- var jThreadObj = (Dictionary<string, object>)jThreadTok;
- var thread = new Thread (jThreadObj, storyContext);
- _threads.Add (thread);
- }
- _threadCounter = (int)jObject ["threadCounter"];
- _startOfRoot = Pointer.StartOf(storyContext.rootContentContainer);
- }
- public void WriteJson(SimpleJson.Writer w)
- {
- w.WriteObject(writer =>
- {
- writer.WritePropertyStart("threads");
- {
- writer.WriteArrayStart();
- foreach (CallStack.Thread thread in _threads)
- {
- thread.WriteJson(writer);
- }
- writer.WriteArrayEnd();
- }
- writer.WritePropertyEnd();
- writer.WritePropertyStart("threadCounter");
- {
- writer.Write(_threadCounter);
- }
- writer.WritePropertyEnd();
- });
-
- }
- public void PushThread()
- {
- var newThread = currentThread.Copy ();
- _threadCounter++;
- newThread.threadIndex = _threadCounter;
- _threads.Add (newThread);
- }
- public Thread ForkThread()
- {
- var forkedThread = currentThread.Copy();
- _threadCounter++;
- forkedThread.threadIndex = _threadCounter;
- return forkedThread;
- }
- public void PopThread()
- {
- if (canPopThread) {
- _threads.Remove (currentThread);
- } else {
- throw new System.Exception("Can't pop thread");
- }
- }
- public bool canPopThread
- {
- get {
- return _threads.Count > 1 && !elementIsEvaluateFromGame;
- }
- }
- public bool elementIsEvaluateFromGame
- {
- get {
- return currentElement.type == PushPopType.FunctionEvaluationFromGame;
- }
- }
- public void Push(PushPopType type, int externalEvaluationStackHeight = 0, int outputStreamLengthWithPushed = 0)
- {
-
- var element = new Element (
- type,
- currentElement.currentPointer,
- inExpressionEvaluation: false
- );
- element.evaluationStackHeightWhenPushed = externalEvaluationStackHeight;
- element.functionStartInOuputStream = outputStreamLengthWithPushed;
- callStack.Add (element);
- }
- public bool CanPop(PushPopType? type = null) {
- if (!canPop)
- return false;
-
- if (type == null)
- return true;
-
- return currentElement.type == type;
- }
-
- public void Pop(PushPopType? type = null)
- {
- if (CanPop (type)) {
- callStack.RemoveAt (callStack.Count - 1);
- return;
- } else {
- throw new System.Exception("Mismatched push/pop in Callstack");
- }
- }
-
- public Runtime.Object GetTemporaryVariableWithName(string name, int contextIndex = -1)
- {
- if (contextIndex == -1)
- contextIndex = currentElementIndex+1;
-
- Runtime.Object varValue = null;
- var contextElement = callStack [contextIndex-1];
- if (contextElement.temporaryVariables.TryGetValue (name, out varValue)) {
- return varValue;
- } else {
- return null;
- }
- }
-
- public void SetTemporaryVariable(string name, Runtime.Object value, bool declareNew, int contextIndex = -1)
- {
- if (contextIndex == -1)
- contextIndex = currentElementIndex+1;
- var contextElement = callStack [contextIndex-1];
-
- if (!declareNew && !contextElement.temporaryVariables.ContainsKey(name)) {
- throw new System.Exception ("Could not find temporary variable to set: " + name);
- }
- Runtime.Object oldValue;
- if( contextElement.temporaryVariables.TryGetValue(name, out oldValue) )
- ListValue.RetainListOriginsForAssignment (oldValue, value);
- contextElement.temporaryVariables [name] = value;
- }
-
-
-
-
- public int ContextForVariableNamed(string name)
- {
-
-
- if (currentElement.temporaryVariables.ContainsKey (name)) {
- return currentElementIndex+1;
- }
-
- else {
- return 0;
- }
- }
-
- public Thread ThreadWithIndex(int index)
- {
- return _threads.Find (t => t.threadIndex == index);
- }
- private List<Element> callStack
- {
- get {
- return currentThread.callstack;
- }
- }
- public string callStackTrace {
- get {
- var sb = new System.Text.StringBuilder();
- for(int t=0; t<_threads.Count; t++) {
- var thread = _threads[t];
- var isCurrent = (t == _threads.Count-1);
- sb.AppendFormat("=== THREAD {0}/{1} {2}===\n", (t+1), _threads.Count, (isCurrent ? "(current) ":""));
- for(int i=0; i<thread.callstack.Count; i++) {
- if( thread.callstack[i].type == PushPopType.Function )
- sb.Append(" [FUNCTION] ");
- else
- sb.Append(" [TUNNEL] ");
- var pointer = thread.callstack[i].currentPointer;
- if( !pointer.isNull ) {
- sb.Append("<SOMEWHERE IN ");
- sb.Append(pointer.container.path.ToString());
- sb.AppendLine(">");
- }
- }
- }
- return sb.ToString();
- }
- }
- List<Thread> _threads;
- int _threadCounter;
- Pointer _startOfRoot;
- }
- }