/flight-pack

A demo app using wasm-pack-plugin, drawing flight information on Google Map.

Primary LanguageRustOtherNOASSERTION

flight-pack

A demo app using wasm-pack-plugin, drawing flight information on Google Map.

screenshot

View Demo

1. About

This is a demo WASM app using wasm-pack-plugin to draw airplane flight paths on Google map. The app aims to provide developers an example using wasm-pack-plugin to create WASM apps in Rust, intends to help in solving common issues.

Follows a brief description of what the app does:

  1. JS runs the WASM app, passing a list of airports for which we want arrivals/departures.
  2. WASM app fetches arrival/departure information from FlightAware's Aero API.
  3. WASM app returns a list of geo-coordinates for which JS can update the map bounds for Google Map.
  4. WASM app runs a loop, constantly draws flight paths on a canvas region over the Google Map.

But, wait. Unfortunately, FlightAware's API does not allow client apps to fetch data. As such, this app internally has dummy arrival/departure data, and retrieves the data instead of fetching remotely. Yet, I believe it still serves the purpose of demonstrating how you can manage JSON data in WASM apps. We encounter tons of problems when writing WASM apps, such as when we spawn threads per JS request, or when we handle DOMs using wasm-bindgen and web-sys, etc. Well, I found it hard figuring out for solutions, and I bet you would, too. The app may not give you exact answers to your specific problems, but it should at least give suggestions for possible solutions.

Key Features

Since the app is provided for learning purpose, there are several features that I implemented intentionally so that you may find them useful when you deal with those.

  • Keeps Original Structure
    Tried as much as possible to preserve the default directory structure provided by wasm-pack-plugin. It would be hard for developers to see changes if the project was heavily customized without any traces of what they were like before.
  • Serialize/Deserialize JSON Data
    Serializing/Deserializing JSON structures to communicate with JS is where many developers struggle when developing WASM apps. Also, this app retrieves internally stored dummy JSON data, and that should give you better ideas.
  • Threads
    When using wasm-bindgen, you may not avoid dealing with threads. This app provides several ways passing data over threads, especially, when you want asynchronous tasks in there.
  • Borrowing/Cloning/Mutating
    As you can imagine, we deal with a lot of borrowing when writing in Rust. This app includes examples using some of the commonly used basic types such as Arc, Rc, RefCell along with in-depth comments explaining why they were done the way they were done.

How the App Works

Here, sketches an outline of how the app works:

  1. JS kicks off by creating an instance of App which is an exposed struct for JS to use: app = new wasm.App(el.canvas)
  2. JS calls await app.prepare(airports)
  3. airports is in JSON format given from JS, and it is a list of airports for which JS wants WASM app to fetch arrival/departure information from FlightAware API.
  4. App::prepare(airpots) runs, but it does nothing... Instead, it bypasses the call: Proxy::prepare(this, airports).await
  5. Looking into Proxy, it exposes static functions only. App has proxy: Arc<Proxy> in its data. Whenever App wants Proxy to perform some tasks, App passes this proxy to these functions. proxy is the context in executing these functions, and these static functions can access their own resources using proxy.
  6. When running Proxy::prepare(this, airports).await, it calls Manager::prepare(canvas.ctx, airports).
  7. There, it will deserialize airport (originally JSON), and sets for its own.
  8. For airports, Manager fetches arrival/departure information from FlightAware's Aero API (which, it actually retrieves internally stored dummy data).
  9. Once fetched, it will extract geo-coordinates from the arrivals/departures, and return the list back to JS as a Promise.
  10. Having the geo-coordinates returned from WASM app, JS will use setBounds (of Google Map API) to update the map with a new bounds.
  11. Now, JS runs app.start(). When that happens, App::start() is called, and Proxy::run is called. In there, it will start the animation loop.
  12. Whenever browser size changes, JS will call app.update(map.getBounds().toJSON()).
  13. Runs App::update(), basically, is about running Proxy::set_bounds().

Note on 'App' and 'Proxy'

App does not do much. Instead, Proxy does all the jobs for App. When we want to call asynchronous functions (in WASM apps using wasm-bindgen), we spawn a thread. However, say, you want move self of App into the thread. Unfortunately, Rust does not allow that... To move it into the thread, you need to clone self. Yet, again, that will cost too much... That's why we have proxy: Arc<Proxy> which is the only variable defined in App. Cloning Arc costs you nothing because it means to just prepare another reference to the original. Notice Proxy exposes static functions only. Whenever you want some jobs done using Proxy, you simply clone proxy instance (which will be just a reference) in App, and you pass it to these static functions (we refer to it as this because it sounds perfect for the name). When receiving this, the static functions will use it for their own contexts, looking up their own resources. In another word, static functions begin to behave just like any other member functions. It is just that App holding onto the context.

2. What I Did

See the diff for what I did to the project. It should add more specificity to your understanding.

3. License

Dual-licensed under either of the followings.
Choose at your option.

This demo app contains arrival/departure information fetched from FlightAware Aero API, and are stored in json/arrivals_*.json.

The app also contains json/airports.json which is a list of airports around the world, and was downloaded from Arash Partow's website which is licensed under MIT: