mirror of
https://github.com/Ratstail91/Mementos.git
synced 2025-11-29 10:34:27 +11:00
Committed everything
This commit is contained in:
@@ -0,0 +1,443 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user