0

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.

Community
  • 1
  • 1
SparkFountain
  • 2,110
  • 15
  • 35
  • What does `LoadImage` return? Is this a custom function you designed or from an existing library? If it is the later please add a link to the library, otherwise please include the full signature with all optional parameters and return types. – Igor Jan 27 '20 at 13:46
  • Observables seem like overkill for this, a promise would suffice for `loadImage()`. – Patrick Roberts Jan 27 '20 at 14:01
  • @Igor I added the whole `loadImage` method at the bottom of my post. It's quite long but it seemed illogical to shorten it without losing context. – SparkFountain Jan 27 '20 at 14:01
  • Indeed you could use a promise instead. Use `then` or `subscribe` and place the next call in that callback. See also [How do I return the response from an asynchronous call?](https://stackoverflow.com/q/14220321/1260204) – Igor Jan 27 '20 at 14:03
  • `LoadImage("palm.png").subscribe(loadedImage => {image = loadedImage; DrawImage image, 250, 50;} );` – Igor Jan 27 '20 at 14:04
  • Does this answer your question? [How do I return the response from an asynchronous call?](https://stackoverflow.com/questions/14220321/how-do-i-return-the-response-from-an-asynchronous-call) – Igor Jan 27 '20 at 14:05
  • @Igor Unfortunately not. I know how to handle promises and observables in general, but I have some trouble to pass parameters to function calls not by value but by reference... Difficult to explain; maybe I'll adapt my question and add some code examples of what I tried and why it does not behave as intended. – SparkFountain Jan 28 '20 at 08:15

0 Answers0