TL;DR:
How can I execute a set of pre-defined asynchronous functions one by one, where each function call should always use the current variable values and not just the initial values?
Detailed Question
In my current project I implement a simple game scripting language. It should be lexed and parsed to generate JavaScript function calls (or more specifically TypeScript). These are the most important requirements:
- all scripted statements should be converted to equivalent JavaScript function calls
- functions should be executed in order (synchronously)
- functions can behave asynchronously (e. g. loading images)
For instance, consider the following piece of scripted code:
Global image = LoadImage("palm.png") ; load an image and save the reference in global variable "image"
DrawImage image, 250, 50 ; draw the image from reference "image" at position x=250, y=50)
These two statements should be executed in order (first load an image, then draw it somewhere on the screen). To "convert" that code to JavaScript, I have to statically analyze the structure of each statement and find a data structure to keep track of the current game state.
My approach is to use an array of statements where I push all parsed statements, as well as an object that holds all global variables:
let statements = [];
let globals = {};
Now, for each parsed statement I wish to push a JavaScript function call into statements
so that I can later execute the statements one by one. However, the functions can take an undefined amount of time until they are "done", like e. g. the LoadImage
function that loads a resource and uses a callback to signal when it has finished.
Moreover, the values of global variables can change over time. In this case, the variable image
will have no value at all before the statements are executed - but after executing the first statement (LoadImage), it will have a reference to an image (say a DOM reference). If the second statement gets executed afterwards, it should refer to the current value of the image
variable. However, I still have to pre-define the function call on DrawImage already immediately after parsing.
Now my question is, how can I combine a static set of function calls (that are generated by a parser) with dynamic variable values, such that the functions always use the newest variable values at the time of their execution?
And what would be the best way to wait for the next statement until the previous one is done? My current approach for the latter is to use Observables, like in this implementation for LoadImage
in TypeScript:
export interface GameImage2D {
name: string,
element: HTMLImageElement,
maskedElement?: HTMLImageElement,
maskColor?: {
red: number,
green: number,
blue: number
},
width: number,
height: number,
handle: {
x: number,
y: number
},
rotation: number
}
loadImage(filePath: string): Observable <GameImage2D> {
return new Observable <GameImage2D> ((observer: Subscriber <GameImage2D> ) => {
//info: the responseType conversion to JSON is a workaround, see https://github.com/angular/angular/issues/18586
this.http.get <Blob> (`${this.environment.getServer()}${filePath}`, { responseType: 'blob'
as 'json' })
.subscribe((imageAsBlob: Blob) => {
let reader = new FileReader();
reader.addEventListener('load', () => {
let htmlImage: HTMLImageElement = document.createElement('img') as HTMLImageElement;
htmlImage.onload = () => {
let autoMidHandleActive = this.autoMidHandleActive();
observer.next({
name: '',
element: htmlImage,
width: htmlImage.width,
height: htmlImage.height,
handle: {
x: autoMidHandleActive ? htmlImage.width / 2 : 0,
y: autoMidHandleActive ? htmlImage.height / 2 : 0
},
rotation: 0
});
observer.complete();
};
htmlImage.src = reader.result as string;
}, false);
if (imageAsBlob) {
reader.readAsDataURL(imageAsBlob);
}
});
});
}
Thank you very much for your help.