Newer
Older
TheVengeance-Project-IADE-Unity2D / Assets / Ink / InkLibs / InkRuntime / CallStack.cs
  1. using System.Collections.Generic;
  2. using System.Linq;
  3. using System.Diagnostics;
  4. namespace Ink.Runtime
  5. {
  6. public class CallStack
  7. {
  8. public class Element
  9. {
  10. public Pointer currentPointer;
  11. public bool inExpressionEvaluation;
  12. public Dictionary<string, Runtime.Object> temporaryVariables;
  13. public PushPopType type;
  14. // When this callstack element is actually a function evaluation called from the game,
  15. // we need to keep track of the size of the evaluation stack when it was called
  16. // so that we know whether there was any return value.
  17. public int evaluationStackHeightWhenPushed;
  18. // When functions are called, we trim whitespace from the start and end of what
  19. // they generate, so we make sure know where the function's start and end are.
  20. public int functionStartInOuputStream;
  21. public Element(PushPopType type, Pointer pointer, bool inExpressionEvaluation = false) {
  22. this.currentPointer = pointer;
  23. this.inExpressionEvaluation = inExpressionEvaluation;
  24. this.temporaryVariables = new Dictionary<string, Object>();
  25. this.type = type;
  26. }
  27. public Element Copy()
  28. {
  29. var copy = new Element (this.type, currentPointer, this.inExpressionEvaluation);
  30. copy.temporaryVariables = new Dictionary<string,Object>(this.temporaryVariables);
  31. copy.evaluationStackHeightWhenPushed = evaluationStackHeightWhenPushed;
  32. copy.functionStartInOuputStream = functionStartInOuputStream;
  33. return copy;
  34. }
  35. }
  36. public class Thread
  37. {
  38. public List<Element> callstack;
  39. public int threadIndex;
  40. public Pointer previousPointer;
  41. public Thread() {
  42. callstack = new List<Element>();
  43. }
  44. public Thread(Dictionary<string, object> jThreadObj, Story storyContext) : this() {
  45. threadIndex = (int) jThreadObj ["threadIndex"];
  46. List<object> jThreadCallstack = (List<object>) jThreadObj ["callstack"];
  47. foreach (object jElTok in jThreadCallstack) {
  48. var jElementObj = (Dictionary<string, object>)jElTok;
  49. PushPopType pushPopType = (PushPopType)(int)jElementObj ["type"];
  50. Pointer pointer = Pointer.Null;
  51. string currentContainerPathStr = null;
  52. object currentContainerPathStrToken;
  53. if (jElementObj.TryGetValue ("cPath", out currentContainerPathStrToken)) {
  54. currentContainerPathStr = currentContainerPathStrToken.ToString ();
  55. var threadPointerResult = storyContext.ContentAtPath (new Path (currentContainerPathStr));
  56. pointer.container = threadPointerResult.container;
  57. pointer.index = (int)jElementObj ["idx"];
  58. if (threadPointerResult.obj == null)
  59. 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?");
  60. else if (threadPointerResult.approximate)
  61. 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?");
  62. }
  63. bool inExpressionEvaluation = (bool)jElementObj ["exp"];
  64. var el = new Element (pushPopType, pointer, inExpressionEvaluation);
  65. object temps;
  66. if ( jElementObj.TryGetValue("temp", out temps) ) {
  67. el.temporaryVariables = Json.JObjectToDictionaryRuntimeObjs((Dictionary<string, object>)temps);
  68. } else {
  69. el.temporaryVariables.Clear();
  70. }
  71. callstack.Add (el);
  72. }
  73. object prevContentObjPath;
  74. if( jThreadObj.TryGetValue("previousContentObject", out prevContentObjPath) ) {
  75. var prevPath = new Path((string)prevContentObjPath);
  76. previousPointer = storyContext.PointerAtPath(prevPath);
  77. }
  78. }
  79. public Thread Copy() {
  80. var copy = new Thread ();
  81. copy.threadIndex = threadIndex;
  82. foreach(var e in callstack) {
  83. copy.callstack.Add(e.Copy());
  84. }
  85. copy.previousPointer = previousPointer;
  86. return copy;
  87. }
  88. public void WriteJson(SimpleJson.Writer writer)
  89. {
  90. writer.WriteObjectStart();
  91. // callstack
  92. writer.WritePropertyStart("callstack");
  93. writer.WriteArrayStart();
  94. foreach (CallStack.Element el in callstack)
  95. {
  96. writer.WriteObjectStart();
  97. if(!el.currentPointer.isNull) {
  98. writer.WriteProperty("cPath", el.currentPointer.container.path.componentsString);
  99. writer.WriteProperty("idx", el.currentPointer.index);
  100. }
  101. writer.WriteProperty("exp", el.inExpressionEvaluation);
  102. writer.WriteProperty("type", (int)el.type);
  103. if(el.temporaryVariables.Count > 0) {
  104. writer.WritePropertyStart("temp");
  105. Json.WriteDictionaryRuntimeObjs(writer, el.temporaryVariables);
  106. writer.WritePropertyEnd();
  107. }
  108. writer.WriteObjectEnd();
  109. }
  110. writer.WriteArrayEnd();
  111. writer.WritePropertyEnd();
  112. // threadIndex
  113. writer.WriteProperty("threadIndex", threadIndex);
  114. if (!previousPointer.isNull)
  115. {
  116. writer.WriteProperty("previousContentObject", previousPointer.Resolve().path.ToString());
  117. }
  118. writer.WriteObjectEnd();
  119. }
  120. }
  121. public List<Element> elements {
  122. get {
  123. return callStack;
  124. }
  125. }
  126. public int depth {
  127. get {
  128. return elements.Count;
  129. }
  130. }
  131. public Element currentElement {
  132. get {
  133. var thread = _threads [_threads.Count - 1];
  134. var cs = thread.callstack;
  135. return cs [cs.Count - 1];
  136. }
  137. }
  138. public int currentElementIndex {
  139. get {
  140. return callStack.Count - 1;
  141. }
  142. }
  143. public Thread currentThread
  144. {
  145. get {
  146. return _threads [_threads.Count - 1];
  147. }
  148. set {
  149. Debug.Assert (_threads.Count == 1, "Shouldn't be directly setting the current thread when we have a stack of them");
  150. _threads.Clear ();
  151. _threads.Add (value);
  152. }
  153. }
  154. public bool canPop {
  155. get {
  156. return callStack.Count > 1;
  157. }
  158. }
  159. public CallStack (Story storyContext)
  160. {
  161. _startOfRoot = Pointer.StartOf(storyContext.rootContentContainer);
  162. Reset();
  163. }
  164. public CallStack(CallStack toCopy)
  165. {
  166. _threads = new List<Thread> ();
  167. foreach (var otherThread in toCopy._threads) {
  168. _threads.Add (otherThread.Copy ());
  169. }
  170. _threadCounter = toCopy._threadCounter;
  171. _startOfRoot = toCopy._startOfRoot;
  172. }
  173. public void Reset()
  174. {
  175. _threads = new List<Thread>();
  176. _threads.Add(new Thread());
  177. _threads[0].callstack.Add(new Element(PushPopType.Tunnel, _startOfRoot));
  178. }
  179. // Unfortunately it's not possible to implement jsonToken since
  180. // the setter needs to take a Story as a context in order to
  181. // look up objects from paths for currentContainer within elements.
  182. public void SetJsonToken(Dictionary<string, object> jObject, Story storyContext)
  183. {
  184. _threads.Clear ();
  185. var jThreads = (List<object>) jObject ["threads"];
  186. foreach (object jThreadTok in jThreads) {
  187. var jThreadObj = (Dictionary<string, object>)jThreadTok;
  188. var thread = new Thread (jThreadObj, storyContext);
  189. _threads.Add (thread);
  190. }
  191. _threadCounter = (int)jObject ["threadCounter"];
  192. _startOfRoot = Pointer.StartOf(storyContext.rootContentContainer);
  193. }
  194. public void WriteJson(SimpleJson.Writer w)
  195. {
  196. w.WriteObject(writer =>
  197. {
  198. writer.WritePropertyStart("threads");
  199. {
  200. writer.WriteArrayStart();
  201. foreach (CallStack.Thread thread in _threads)
  202. {
  203. thread.WriteJson(writer);
  204. }
  205. writer.WriteArrayEnd();
  206. }
  207. writer.WritePropertyEnd();
  208. writer.WritePropertyStart("threadCounter");
  209. {
  210. writer.Write(_threadCounter);
  211. }
  212. writer.WritePropertyEnd();
  213. });
  214. }
  215. public void PushThread()
  216. {
  217. var newThread = currentThread.Copy ();
  218. _threadCounter++;
  219. newThread.threadIndex = _threadCounter;
  220. _threads.Add (newThread);
  221. }
  222. public Thread ForkThread()
  223. {
  224. var forkedThread = currentThread.Copy();
  225. _threadCounter++;
  226. forkedThread.threadIndex = _threadCounter;
  227. return forkedThread;
  228. }
  229. public void PopThread()
  230. {
  231. if (canPopThread) {
  232. _threads.Remove (currentThread);
  233. } else {
  234. throw new System.Exception("Can't pop thread");
  235. }
  236. }
  237. public bool canPopThread
  238. {
  239. get {
  240. return _threads.Count > 1 && !elementIsEvaluateFromGame;
  241. }
  242. }
  243. public bool elementIsEvaluateFromGame
  244. {
  245. get {
  246. return currentElement.type == PushPopType.FunctionEvaluationFromGame;
  247. }
  248. }
  249. public void Push(PushPopType type, int externalEvaluationStackHeight = 0, int outputStreamLengthWithPushed = 0)
  250. {
  251. // When pushing to callstack, maintain the current content path, but jump out of expressions by default
  252. var element = new Element (
  253. type,
  254. currentElement.currentPointer,
  255. inExpressionEvaluation: false
  256. );
  257. element.evaluationStackHeightWhenPushed = externalEvaluationStackHeight;
  258. element.functionStartInOuputStream = outputStreamLengthWithPushed;
  259. callStack.Add (element);
  260. }
  261. public bool CanPop(PushPopType? type = null) {
  262. if (!canPop)
  263. return false;
  264. if (type == null)
  265. return true;
  266. return currentElement.type == type;
  267. }
  268. public void Pop(PushPopType? type = null)
  269. {
  270. if (CanPop (type)) {
  271. callStack.RemoveAt (callStack.Count - 1);
  272. return;
  273. } else {
  274. throw new System.Exception("Mismatched push/pop in Callstack");
  275. }
  276. }
  277. // Get variable value, dereferencing a variable pointer if necessary
  278. public Runtime.Object GetTemporaryVariableWithName(string name, int contextIndex = -1)
  279. {
  280. if (contextIndex == -1)
  281. contextIndex = currentElementIndex+1;
  282. Runtime.Object varValue = null;
  283. var contextElement = callStack [contextIndex-1];
  284. if (contextElement.temporaryVariables.TryGetValue (name, out varValue)) {
  285. return varValue;
  286. } else {
  287. return null;
  288. }
  289. }
  290. public void SetTemporaryVariable(string name, Runtime.Object value, bool declareNew, int contextIndex = -1)
  291. {
  292. if (contextIndex == -1)
  293. contextIndex = currentElementIndex+1;
  294. var contextElement = callStack [contextIndex-1];
  295. if (!declareNew && !contextElement.temporaryVariables.ContainsKey(name)) {
  296. throw new System.Exception ("Could not find temporary variable to set: " + name);
  297. }
  298. Runtime.Object oldValue;
  299. if( contextElement.temporaryVariables.TryGetValue(name, out oldValue) )
  300. ListValue.RetainListOriginsForAssignment (oldValue, value);
  301. contextElement.temporaryVariables [name] = value;
  302. }
  303. // Find the most appropriate context for this variable.
  304. // Are we referencing a temporary or global variable?
  305. // Note that the compiler will have warned us about possible conflicts,
  306. // so anything that happens here should be safe!
  307. public int ContextForVariableNamed(string name)
  308. {
  309. // Current temporary context?
  310. // (Shouldn't attempt to access contexts higher in the callstack.)
  311. if (currentElement.temporaryVariables.ContainsKey (name)) {
  312. return currentElementIndex+1;
  313. }
  314. // Global
  315. else {
  316. return 0;
  317. }
  318. }
  319. public Thread ThreadWithIndex(int index)
  320. {
  321. return _threads.Find (t => t.threadIndex == index);
  322. }
  323. private List<Element> callStack
  324. {
  325. get {
  326. return currentThread.callstack;
  327. }
  328. }
  329. public string callStackTrace {
  330. get {
  331. var sb = new System.Text.StringBuilder();
  332. for(int t=0; t<_threads.Count; t++) {
  333. var thread = _threads[t];
  334. var isCurrent = (t == _threads.Count-1);
  335. sb.AppendFormat("=== THREAD {0}/{1} {2}===\n", (t+1), _threads.Count, (isCurrent ? "(current) ":""));
  336. for(int i=0; i<thread.callstack.Count; i++) {
  337. if( thread.callstack[i].type == PushPopType.Function )
  338. sb.Append(" [FUNCTION] ");
  339. else
  340. sb.Append(" [TUNNEL] ");
  341. var pointer = thread.callstack[i].currentPointer;
  342. if( !pointer.isNull ) {
  343. sb.Append("<SOMEWHERE IN ");
  344. sb.Append(pointer.container.path.ToString());
  345. sb.AppendLine(">");
  346. }
  347. }
  348. }
  349. return sb.ToString();
  350. }
  351. }
  352. List<Thread> _threads;
  353. int _threadCounter;
  354. Pointer _startOfRoot;
  355. }
  356. }