accelerator-core
Version:
[](https://travis-ci.org/furkleindustries/accelerator-core)
385 lines (305 loc) • 11.4 kB
text/typescript
import {PushPopType} from './PushPop';
import {Path} from './Path';
import {Story} from './Story';
import {StoryException} from './StoryException';
import {JsonSerialisation} from './JsonSerialisation';
import {ListValue} from './Value';
import {StringBuilder} from './StringBuilder';
import {Pointer} from './Pointer';
import {InkObject} from './Object';
import {Debug} from './Debug';
import {tryGetValueFromMap} from './TryGetResult';
import {throwNullException} from './NullException';
export class CallStack{
get elements(){
return this.callStack;
}
get depth(){
return this.elements.length;
}
get currentElement(){
const thread = this._threads[this._threads.length - 1];
const cs = thread.callstack;
return cs[cs.length - 1];
}
get currentElementIndex(){
return this.callStack.length - 1;
}
get currentThread(): CallStack.Thread {
return this._threads[this._threads.length - 1];
}
set currentThread(value: CallStack.Thread){
Debug.Assert(this._threads.length == 1, "Shouldn't be directly setting the current thread when we have a stack of them");
this._threads.length = 0;
this._threads.push(value);
}
get canPop(){
return this.callStack.length > 1;
}
constructor(storyContext: Story)
constructor(toCopy: CallStack)
constructor(){
if (arguments[0] instanceof Story) {
const storyContext = arguments[0];
this._startOfRoot = Pointer.StartOf(storyContext.rootContentContainer);
this.Reset();
} else {
const toCopy = arguments[0];
this._threads = [];
for (const otherThread of toCopy._threads) {
this._threads.push(otherThread.Copy());
}
this._startOfRoot = toCopy._startOfRoot;
}
}
public Reset() {
this._threads = [];
this._threads.push(new CallStack.Thread());
this._threads[0].callstack.push(new CallStack.Element(PushPopType.Tunnel, this._startOfRoot));
}
public SetJsonToken(jObject: any, storyContext: Story){
this._threads.length = 0;
// TODO: (List<object>) jObject ["threads"];
let jThreads: any[] = jObject['threads'];
for (let jThreadTok of jThreads) {
// TODO: var jThreadObj = (Dictionary<string, object>)jThreadTok;
let jThreadObj = jThreadTok;
let thread = new CallStack.Thread(jThreadObj, storyContext);
this._threads.push(thread);
}
// TODO: (int)jObject ["threadCounter"];
this._threadCounter = parseInt(jObject['threadCounter']);
this._startOfRoot = Pointer.StartOf(storyContext.rootContentContainer);
}
public GetJsonToken(){
let jObject: any = {};
let jThreads: any[] = [];
for (let thread of this._threads) {
jThreads.push(thread.jsonToken);
}
jObject['threads'] = jThreads;
jObject['threadCounter'] = this._threadCounter;
return jObject;
}
public PushThread(){
let newThread = this.currentThread.Copy();
this._threadCounter++;
newThread.threadIndex = this._threadCounter;
this._threads.push(newThread);
}
public ForkThread(){
let forkedThread = this.currentThread.Copy();
this._threadCounter++;
forkedThread.threadIndex = this._threadCounter;
return forkedThread;
}
public PopThread(){
if (this.canPopThread) {
this._threads.splice(this._threads.indexOf(this.currentThread), 1);// should be equivalent to a pop()
} else {
throw new Error("Can't pop thread");
}
}
get canPopThread(){
return this._threads.length > 1 && !this.elementIsEvaluateFromGame;
}
get elementIsEvaluateFromGame(){
return this.currentElement.type == PushPopType.FunctionEvaluationFromGame;
}
public Push(type: PushPopType, externalEvaluationStackHeight: number = 0, outputStreamLengthWithPushed: number = 0){
let element = new CallStack.Element(
type,
this.currentElement.currentPointer,
false,
);
element.evaluationStackHeightWhenPushed = externalEvaluationStackHeight;
element.functionStartInOutputStream = outputStreamLengthWithPushed;
this.callStack.push (element);
}
public CanPop(type: PushPopType | null = null){
if (!this.canPop)
return false;
if (type == null)
return true;
return this.currentElement.type == type;
}
public Pop(type: PushPopType | null = null){
if (this.CanPop(type)) {
this.callStack.pop();
return;
} else {
throw new Error('Mismatched push/pop in Callstack');
}
}
public GetTemporaryVariableWithName(name: string | null, contextIndex: number = -1){
if (contextIndex == -1)
contextIndex = this.currentElementIndex + 1;
let contextElement = this.callStack[contextIndex - 1];
let varValue = tryGetValueFromMap(contextElement.temporaryVariables, name, null);
if (varValue.exists) {
return varValue.result;
} else {
return null;
}
}
public SetTemporaryVariable(name: string, value: any, declareNew: boolean, contextIndex: number = -1){
if (contextIndex == -1)
contextIndex = this.currentElementIndex + 1;
let contextElement = this.callStack[contextIndex - 1];
if (!declareNew && !contextElement.temporaryVariables.get(name)) {
throw new StoryException('Could not find temporary variable to set: ' + name);
}
let oldValue = tryGetValueFromMap(contextElement.temporaryVariables, name, null);
if (oldValue.exists)
ListValue.RetainListOriginsForAssignment(oldValue.result, value);
contextElement.temporaryVariables.set(name, value);
}
public ContextForVariableNamed(name: string){
if (this.currentElement.temporaryVariables.get(name)) {
return this.currentElementIndex + 1;
}
else {
return 0;
}
}
// @ts-ignore
public ThreadWithIndex(index: number){
// @ts-ignore
let filtered = this._threads.filter((t) => {
if (t.threadIndex == index) return t;
});
return filtered[0];
}
get callStack(){
return this.currentThread.callstack;
}
get callStackTrace(){
let sb = new StringBuilder();
for (let t = 0; t < this._threads.length; t++) {
let thread = this._threads[t];
let isCurrent = (t == this._threads.length - 1);
sb.AppendFormat('=== THREAD {0}/{1} {2}===\n', (t+1), this._threads.length, (isCurrent ? '(current) ' : ''));
for (let i = 0; i < thread.callstack.length; i++) {
if (thread.callstack[i].type == PushPopType.Function)
sb.Append(' [FUNCTION] ');
else
sb.Append(' [TUNNEL] ');
let pointer = thread.callstack[i].currentPointer;
if(!pointer.isNull) {
sb.Append('<SOMEWHERE IN ');
if (pointer.container === null) { return throwNullException('pointer.container'); }
sb.Append(pointer.container.path.toString());
sb.AppendLine('>');
}
}
}
return sb.toString();
}
public _threads!: CallStack.Thread[]; // Banged because it's initialized in Reset().
public _threadCounter: number = 0;
public _startOfRoot: Pointer = Pointer.Null;
}
export namespace CallStack {
export class Element{
public currentPointer: Pointer;
public inExpressionEvaluation: boolean;
public temporaryVariables: Map<string, InkObject>;
public type: PushPopType;
public evaluationStackHeightWhenPushed: number = 0;
public functionStartInOutputStream: number = 0;
constructor(type: PushPopType, pointer: Pointer, inExpressionEvaluation: boolean = false){
this.currentPointer = pointer.copy();
this.inExpressionEvaluation = inExpressionEvaluation;
this.temporaryVariables = new Map();
this.type = type;
}
public Copy(){
let copy = new Element(this.type, this.currentPointer, this.inExpressionEvaluation);
copy.temporaryVariables = new Map(this.temporaryVariables);
copy.evaluationStackHeightWhenPushed = this.evaluationStackHeightWhenPushed;
copy.functionStartInOutputStream = this.functionStartInOutputStream;
return copy;
}
}
export class Thread{
public callstack: Element[];
public threadIndex: number = 0;
public previousPointer: Pointer = Pointer.Null;
constructor();
constructor(jThreadObj: any, storyContext: Story);
constructor(){
this.callstack = [];
if (arguments[0] && arguments[1]){
let jThreadObj = arguments[0];
let storyContext = arguments[1];
// TODO: (int) jThreadObj['threadIndex'] can raise;
this.threadIndex = parseInt(jThreadObj['threadIndex']);
let jThreadCallstack = jThreadObj['callstack'];
for (let jElTok of jThreadCallstack) {
let jElementObj = jElTok;
// TODO: (int) jElementObj['type'] can raise;
let pushPopType: PushPopType = parseInt(jElementObj['type']);
let pointer = Pointer.Null;
let currentContainerPathStr: string;
// TODO: jElementObj.TryGetValue ("cPath", out currentContainerPathStrToken);
let currentContainerPathStrToken = jElementObj['cPath'];
if (typeof currentContainerPathStrToken !== 'undefined') {
currentContainerPathStr = currentContainerPathStrToken.toString();
let threadPointerResult = storyContext.ContentAtPath(new Path(currentContainerPathStr));
pointer.container = threadPointerResult.container;
pointer.index = parseInt(jElementObj['idx']);
if (threadPointerResult.obj == null)
throw new Error('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) {
if (pointer.container === null) { return throwNullException('pointer.container'); }
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?");
}
}
let inExpressionEvaluation = !!jElementObj['exp'];
let el = new Element(pushPopType, pointer, inExpressionEvaluation);
let jObjTemps = jElementObj['temp'];
el.temporaryVariables = JsonSerialisation.JObjectToDictionaryRuntimeObjs(jObjTemps);
this.callstack.push(el);
}
let prevContentObjPath = jThreadObj['previousContentObject'];
if(typeof prevContentObjPath !== 'undefined') {
let prevPath = new Path(prevContentObjPath.toString());
this.previousPointer = storyContext.PointerAtPath(prevPath);
}
}
}
public Copy(){
let copy = new Thread();
copy.threadIndex = this.threadIndex;
for (let e of this.callstack) {
copy.callstack.push(e.Copy());
}
copy.previousPointer = this.previousPointer.copy();
return copy;
}
get jsonToken(){
let threadJObj: any = {};
let jThreadCallstack: any[] = [];
for (let el of this.callstack) {
let jObj: any = {};
if (!el.currentPointer.isNull) {
if (el.currentPointer.container === null) { return throwNullException('el.currentPointer.container'); }
jObj['cPath'] = el.currentPointer.container.path.componentsString;
jObj['idx'] = el.currentPointer.index;
}
jObj['exp'] = el.inExpressionEvaluation;
jObj['type'] = el.type;
jObj['temp'] = JsonSerialisation.DictionaryRuntimeObjsToJObject(el.temporaryVariables);
jThreadCallstack.push(jObj);
}
threadJObj['callstack'] = jThreadCallstack;
threadJObj['threadIndex'] = this.threadIndex;
if (!this.previousPointer.isNull) {
let resolvedPointer = this.previousPointer.Resolve();
if (resolvedPointer === null) { return throwNullException('this.previousPointer.Resolve()'); }
threadJObj['previousContentObject'] = resolvedPointer.path.toString();
}
return threadJObj;
}
}
}