mirror of
https://github.com/Ratstail91/Mementos.git
synced 2025-11-29 02:24:28 +11:00
444 lines
15 KiB
C#
444 lines
15 KiB
C#
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;
|
|
|
|
// When this callstack element is actually a function evaluation called from the game,
|
|
// we need to keep track of the size of the evaluation stack when it was called
|
|
// so that we know whether there was any return value.
|
|
public int evaluationStackHeightWhenPushed;
|
|
|
|
// When functions are called, we trim whitespace from the start and end of what
|
|
// they generate, so we make sure know where the function's start and end are.
|
|
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();
|
|
|
|
// callstack
|
|
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();
|
|
|
|
// threadIndex
|
|
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));
|
|
}
|
|
|
|
|
|
// Unfortunately it's not possible to implement jsonToken since
|
|
// the setter needs to take a Story as a context in order to
|
|
// look up objects from paths for currentContainer within elements.
|
|
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)
|
|
{
|
|
// When pushing to callstack, maintain the current content path, but jump out of expressions by default
|
|
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");
|
|
}
|
|
}
|
|
|
|
// Get variable value, dereferencing a variable pointer if necessary
|
|
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;
|
|
}
|
|
|
|
// Find the most appropriate context for this variable.
|
|
// Are we referencing a temporary or global variable?
|
|
// Note that the compiler will have warned us about possible conflicts,
|
|
// so anything that happens here should be safe!
|
|
public int ContextForVariableNamed(string name)
|
|
{
|
|
// Current temporary context?
|
|
// (Shouldn't attempt to access contexts higher in the callstack.)
|
|
if (currentElement.temporaryVariables.ContainsKey (name)) {
|
|
return currentElementIndex+1;
|
|
}
|
|
|
|
// Global
|
|
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;
|
|
}
|
|
}
|
|
|