Ozmo game

Phil Trelford's classic ported to Fable

Phil Trelford's classic Ozmo game, originally hosted on BitBucket ported to Fable! Shows how to handle keyboard events and use HTML5 canvas. You can also get it (as a JavaScript app) from the Windows Store. View the raw source code on GitHub. To play the game, use left and right keys!

This demo shows a simple game written using Fable and HTML5 canvas. One interesting aspect of the game is the asynchronous game loop, which updates the game state 60 times per second in a loop. This is implemented using F# asynchronous workflows, which make it possible to capture the logic as a recursive function, rather than using mutable state.

The Ozmo game uses the Keyboard helpers from the Mario sample, so if you want to see those, check out the Mario sample first - it is also simpler, so you can check it out for lighter introduction to Fable and F#.

Drawing the world

The first few functions in the game deal with rendering. The world consists of two gradients (with yellow orange gradient in the sky and gray gradient for the atmosphere) and a filled black rectangle. The drawGrd function draws a gradient and drawBg renders the world. We also need drawText for printing text when the game finishes:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
/// Draw gradient between two Y offsets and two colours
let drawGrd (ctx:CanvasRenderingContext2D)
    (canvas:HTMLCanvasElement) (y0,y1) (c0,c1) =
  let grd = ctx.createLinearGradient(0.,y0,0.,y1)
  grd.addColorStop(0.,c0)
  grd.addColorStop(1.,c1)
  ctx.fillStyle <- U3.Case2 grd
  ctx.fillRect(0.,y0, canvas.width, y1- y0)

/// Draw background of the Ozmo game
let drawBg ctx canvas =
  drawGrd ctx canvas
    (0.,atmosHeight) ("yellow","orange")
  drawGrd ctx canvas
    (atmosHeight, canvas.height-floorHeight)
    ("grey","white")
  ctx.fillStyle <- U3.Case1 "black"
  ctx.fillRect
    ( 0.,canvas.height-floorHeight,
      canvas.width,floorHeight )

/// Draw the specified text (when game finishes)
let drawText(text,x,y) =
  ctx.fillStyle <- U3.Case1 "white"
  ctx.font <- "bold 40pt";
  ctx.fillText(text, x, y)

Representing and drawing blobs

Each of the balls in the game is represented by a Blob value that stores the X and Y coordinates, size of the blob (radius), its colour and current speed. The type is used for both falling blobs and for the player's blob:

1: 
2: 
3: 
4: 
type Blob =
  { X:float; Y:float;
    vx:float; vy:float;
    Radius:float; color:string }

Drawing blob on the canvas is quite easy - the following function does that using the arc function of the 2D rendering context of the canvas:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
let drawBlob (ctx:CanvasRenderingContext2D)
    (canvas:HTMLCanvasElement) (blob:Blob) =
  ctx.beginPath()
  ctx.arc
    ( blob.X, canvas.height - (blob.Y + floorHeight + blob.Radius),
      blob.Radius, 0., 2. * System.Math.PI, false )
  ctx.fillStyle <- U3.Case1 blob.color
  ctx.fill()
  ctx.lineWidth <- 3.
  ctx.strokeStyle <- U3.Case1 blob.color
  ctx.stroke()

Falling blobs and collisions

The next step is to define the physics for the game. This consists of several functions that update the Blob objects and are composed to apply all rules of physics in the main game loop.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
/// Apply key effects on Player's blob - changes X speed
let direct (dx,dy) (blob:Blob) =
  { blob with vx = blob.vx + (float dx)/4.0 }

/// Apply gravity on falling blobs - gets faster every step
let gravity (blob:Blob) =
  if blob.Y > 0. then { blob with vy = blob.vy - 0.1 }
  else blob

/// Bounde Player's blob off the wall if it hits it
let bounce (blob:Blob) =
  let n = width
  if blob.X < 0. then
    { blob with X = -blob.X; vx = -blob.vx }
  elif (blob.X > n) then
    { blob with X = n - (blob.X - n); vx = -blob.vx }
  else blob

/// Move blob by one step - adds X and Y
/// velocities to the X and Y coordinates
let move (blob:Blob) =
  { blob with
      X = blob.X + blob.vx
      Y = max 0.0 (blob.Y + blob.vy) }

The above functions capture the individual aspects of the movement. The following put everything together and handle steps of Player's blob and also collision detection.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
/// Apply step on Player's blob. Composes above functions.
let step dir blob =
  blob |> direct dir |> move |> bounce

/// Check whether two blobs collide
let collide (a:Blob) (b:Blob) =
  let dx = (a.X - b.X)*(a.X - b.X)
  let dy = (a.Y - b.Y)*(a.Y - b.Y)
  let dist = sqrt(dx + dy)
  dist < abs(a.Radius - b.Radius)

/// Remove all falling blobs that hit Player's blob
let absorb (blob:Blob) (drops:Blob list) =
  drops |> List.filter (fun drop ->
    collide blob drop |> not )

Game logic helpers

Next, we define a couple of helpers for generating and updating the falling blobs. We have black growing blobs and white shrinking blobs. The newGrow and newShrink functions are used to generate new blobs:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
let grow = "black"
let shrink = "white"

let newDrop color =
  { X = rand()*width*0.8 + (width*0.1)
    Y=600.; Radius=10.; vx=0.; vy = 0.0
    color=color }

let newGrow () = newDrop grow
let newShrink () = newDrop shrink

Inside the game loop, we will generate blobs randomly, but we keep a counter of ticks to make sure that we do not generate new blobs too often. The updateDrops function takes current drops and a countdown and returns a pair with new drops and a new countdown. It implements simple logic:

  • If we generated drop in last 8 steps, do nothing and decrement counter
  • Roll an 8 sided dice and if we get 1, generate new blob (2/3 are shrinkind and 1/3 are growing)
  • Otherwise, do nothing and return previous state
 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
/// Update drops and countdown in each step
let updateDrops drops countdown =
  if countdown > 0 then
    drops, countdown - 1
  elif floor(rand()*8.) = 0. then
    let drop =
      if floor(rand()*3.) = 0. then newGrow()
      else newShrink()
    drop::drops, 8
  else drops, countdown

/// Count growing and shrinking drops in the list
let countDrops drops =
  let count color =
    drops
    |> List.filter (fun drop -> drop.color = color)
    |> List.length
  count grow, count shrink

Asynchronous game loop

The asynchronous game loop is perhaps the most interesting part of the source code. Fable supports F# asynchronous workflows, which give us a way to write non-blocking loop that includes sleeping in the middle, so you can write long-running processes as a recursive loop rather than using timers and callbacks.

The following diagram illustrates the game loop:

1: 
2: 
3: 
4: 
5: 
6: 
7: 
(start)        +----(tick)---+
   \           |             |
     +------+  |  +--------+ |   +-----------+
  +->| game |--+->| update |-+-->| completed |<-+
  |  +------+     +--------+     +-----------+  |
  |                                             |
  +-----------(after 10 seconds)----------------+

There are three states in which the game can be:

  • After starting, the game state initializes the Player's blob and starts the game
  • The update loop is active when the game is running. It calls itself recursively until the game ends.
  • After finishing, the completed state displays a message and sleeps for 10 seconds before starting a new game.

Using asynchronous workflows, the state machine can be represented using 3 mutually recursive functions, each representing one of the states. The game and completed states are simple:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
/// Starts a new game
let rec game () = async {
  let blob =
    { X = 300.; Y=0.; Radius=50.;
      vx=0.; vy=0.; color="black" }
  return! update blob [newGrow ()] 0 }

/// Displays message and sleeps for 10 sec
and completed () = async {
  drawText ("COMPLETED",320.,300.)
  do! Async.Sleep 10000
  return! game () }

Note that we are using let rec .. and, which lets us write multiple recursive functions that can call each other. The completed function calls game after 10 seconds using return! (representing an asynchronous tail-call) and the game function calls update with the initial state. The update loop looks as follows:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
30: 
31: 
32: 
33: 
34: 
35: 
36: 
/// Keeps current state for Player's blob, falling
/// drops and the countdown since last drop was generated
and update blob drops countdown = async {
  // Update the drops & countdown
  let drops, countdown = updateDrops drops countdown

  // Count drops, apply physics and count them again
  let beforeGrow, beforeShrink = countDrops drops
  let drops =
    drops
    |> List.map (gravity >> move)
    |> absorb blob
  let afterGrow, afterShrink = countDrops drops
  let drops = drops |> List.filter (fun blob -> blob.Y > 0.)

  // Calculate new player's size based on absorbed drops
  let radius = blob.Radius + float (beforeGrow - afterGrow) *4.
  let radius = radius - float (beforeShrink - afterShrink) * 4.
  let radius = max 5.0 radius

  // Update radius and apply keyboard events
  let blob = { blob with Radius = radius }
  let blob = blob |> step (Keyboard.arrows())

  // Render the new game state
  drawBg ctx canvas
  for drop in drops do drawBlob ctx canvas drop
  drawBlob ctx canvas blob

  // If the game completed, switch state
  // otherwise sleep and update recursively!
  if blob.Radius > 150. then
    return! completed()
  else
    do! Async.Sleep(int (1000. / 60.))
    return! update blob drops countdown }

The last thing that we need to do is to start the game in the initial game state using Async.StartImmediate:

1: 
game () |> Async.StartImmediate
namespace Fable
namespace Fable.Core
namespace Fable.Import
module Browser

from Fable.Import
Multiple items
type EmitAttribute =
  inherit Attribute
  private new : unit -> EmitAttribute
  new : macro:string -> EmitAttribute
  new : emitterType:Type * methodName:string -> EmitAttribute

Full name: Fable.Core.EmitAttribute

--------------------
new : macro:string -> EmitAttribute
new : emitterType:System.Type * methodName:string -> EmitAttribute
val rand : unit -> float

Full name: Ozmo.rand
Multiple items
val float : value:'T -> float (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.float

--------------------
type float = System.Double

Full name: Microsoft.FSharp.Core.float

--------------------
type float<'Measure> = float

Full name: Microsoft.FSharp.Core.float<_>
val failwith : message:string -> 'T

Full name: Microsoft.FSharp.Core.Operators.failwith
val mutable keysPressed : Set<int>

Full name: Ozmo.Keyboard.keysPressed
Multiple items
module Set

from Microsoft.FSharp.Collections

--------------------
type Set<'T (requires comparison)> =
  interface IComparable
  interface IEnumerable
  interface IEnumerable<'T>
  interface ICollection<'T>
  new : elements:seq<'T> -> Set<'T>
  member Add : value:'T -> Set<'T>
  member Contains : value:'T -> bool
  override Equals : obj -> bool
  member IsProperSubsetOf : otherSet:Set<'T> -> bool
  member IsProperSupersetOf : otherSet:Set<'T> -> bool
  ...

Full name: Microsoft.FSharp.Collections.Set<_>

--------------------
new : elements:seq<'T> -> Set<'T>
val empty<'T (requires comparison)> : Set<'T> (requires comparison)

Full name: Microsoft.FSharp.Collections.Set.empty
val code : x:int -> int

Full name: Ozmo.Keyboard.code
val x : int
member Set.Contains : value:'T -> bool
val arrows : unit -> int * int

Full name: Ozmo.Keyboard.arrows
val update : e:KeyboardEvent * pressed:bool -> 'a (requires 'a : null)

Full name: Ozmo.Keyboard.update
val e : KeyboardEvent
Multiple items
val KeyboardEvent : KeyboardEventType

Full name: Fable.Import.Browser.KeyboardEvent

--------------------
type KeyboardEvent =
  interface
    inherit UIEvent
    abstract member getModifierState : keyArg:string -> bool
    abstract member DOM_KEY_LOCATION_JOYSTICK : float
    abstract member DOM_KEY_LOCATION_LEFT : float
    abstract member DOM_KEY_LOCATION_MOBILE : float
    abstract member DOM_KEY_LOCATION_NUMPAD : float
    abstract member DOM_KEY_LOCATION_RIGHT : float
    abstract member DOM_KEY_LOCATION_STANDARD : float
    abstract member altKey : bool
    abstract member char : string
    ...
  end

Full name: Fable.Import.Browser.KeyboardEvent
val pressed : bool
val keyCode : int
Multiple items
val int : value:'T -> int (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.int

--------------------
type int = int32

Full name: Microsoft.FSharp.Core.int

--------------------
type int<'Measure> = int

Full name: Microsoft.FSharp.Core.int<_>
property KeyboardEvent.keyCode: float
val op : (int -> Set<int> -> Set<int>)
val add : value:'T -> set:Set<'T> -> Set<'T> (requires comparison)

Full name: Microsoft.FSharp.Collections.Set.add
val remove : value:'T -> set:Set<'T> -> Set<'T> (requires comparison)

Full name: Microsoft.FSharp.Collections.Set.remove
val init : unit -> unit

Full name: Ozmo.Keyboard.init
val window : Window

Full name: Fable.Import.Browser.window
abstract member Window.addEventListener_keydown : listener:System.Func<KeyboardEvent,obj> * ?useCapture:bool -> unit
abstract member Window.addEventListener_keyup : listener:System.Func<KeyboardEvent,obj> * ?useCapture:bool -> unit
val width : float

Full name: Ozmo.width


 The width of the canvas
val height : float

Full name: Ozmo.height


 The height of the canvas
val floorHeight : float

Full name: Ozmo.floorHeight


 Height of the floor - the bottom black part
val atmosHeight : float

Full name: Ozmo.atmosHeight


 Height of the atmosphere - the yellow gradient
module Keyboard

from Ozmo
val canvas : HTMLCanvasElement

Full name: Ozmo.canvas
val document : Document

Full name: Fable.Import.Browser.document
abstract member Document.getElementsByTagName_canvas : unit -> NodeListOf<HTMLCanvasElement>
val ctx : CanvasRenderingContext2D

Full name: Ozmo.ctx
abstract member HTMLCanvasElement.getContext_2d : unit -> CanvasRenderingContext2D
property HTMLCanvasElement.width: float
property HTMLCanvasElement.height: float
val drawGrd : ctx:CanvasRenderingContext2D -> canvas:HTMLCanvasElement -> y0:float * y1:float -> c0:string * c1:string -> unit

Full name: Ozmo.drawGrd


 Draw gradient between two Y offsets and two colours
val ctx : CanvasRenderingContext2D
Multiple items
val CanvasRenderingContext2D : CanvasRenderingContext2DType

Full name: Fable.Import.Browser.CanvasRenderingContext2D

--------------------
type CanvasRenderingContext2D =
  interface
    abstract member arc : x:float * y:float * radius:float * startAngle:float * endAngle:float * ?anticlockwise:bool -> unit
    abstract member arcTo : x1:float * y1:float * x2:float * y2:float * radius:float -> unit
    abstract member beginPath : unit -> unit
    abstract member bezierCurveTo : cp1x:float * cp1y:float * cp2x:float * cp2y:float * x:float * y:float -> unit
    abstract member clearRect : x:float * y:float * w:float * h:float -> unit
    abstract member clip : ?fillRule:string -> unit
    abstract member closePath : unit -> unit
    abstract member createImageData : imageDataOrSw:U2<float,ImageData> * ?sh:float -> ImageData
    abstract member createLinearGradient : x0:float * y0:float * x1:float * y1:float -> CanvasGradient
    abstract member createPattern : image:U3<HTMLImageElement,HTMLCanvasElement,HTMLVideoElement> * repetition:string -> CanvasPattern
    ...
  end

Full name: Fable.Import.Browser.CanvasRenderingContext2D
val canvas : HTMLCanvasElement
Multiple items
val HTMLCanvasElement : HTMLCanvasElementType

Full name: Fable.Import.Browser.HTMLCanvasElement

--------------------
type HTMLCanvasElement =
  interface
    inherit HTMLElement
    abstract member getContext : contextId:string * [<ParamArray>] args:obj [] -> U2<CanvasRenderingContext2D,WebGLRenderingContext>
    abstract member getContext_2d : unit -> CanvasRenderingContext2D
    abstract member ( getContext_experimental-webgl ) : unit -> WebGLRenderingContext
    abstract member height : float
    abstract member width : float
    abstract member msToBlob : unit -> Blob
    abstract member height : float with set
    abstract member width : float with set
    abstract member toBlob : unit -> Blob
    ...
  end

Full name: Fable.Import.Browser.HTMLCanvasElement
val y0 : float
val y1 : float
val c0 : string
val c1 : string
val grd : CanvasGradient
abstract member CanvasRenderingContext2D.createLinearGradient : x0:float * y0:float * x1:float * y1:float -> CanvasGradient
abstract member CanvasGradient.addColorStop : offset:float * color:string -> unit
property CanvasRenderingContext2D.fillStyle: U3<string,CanvasGradient,CanvasPattern>
type U3<'a,'b,'c> =
  | Case1 of 'a
  | Case2 of 'b
  | Case3 of 'c

Full name: Fable.Core.U3<_,_,_>
union case U3.Case2: 'b -> U3<'a,'b,'c>
abstract member CanvasRenderingContext2D.fillRect : x:float * y:float * w:float * h:float -> unit
val drawBg : ctx:CanvasRenderingContext2D -> canvas:HTMLCanvasElement -> unit

Full name: Ozmo.drawBg


 Draw background of the Ozmo game
union case U3.Case1: 'a -> U3<'a,'b,'c>
val drawText : text:string * x:float * y:float -> unit

Full name: Ozmo.drawText


 Draw the specified text (when game finishes)
val text : string
val x : float
val y : float
property CanvasRenderingContext2D.font: string
abstract member CanvasRenderingContext2D.fillText : text:string * x:float * y:float * ?maxWidth:float -> unit
Multiple items
val Blob : BlobType

Full name: Fable.Import.Browser.Blob

--------------------
type Blob =
  {X: float;
   Y: float;
   vx: float;
   vy: float;
   Radius: float;
   color: string;}

Full name: Ozmo.Blob
Blob.X: float
Blob.Y: float
Blob.vx: float
Blob.vy: float
Blob.Radius: float
Blob.color: string
Multiple items
val string : value:'T -> string

Full name: Microsoft.FSharp.Core.Operators.string

--------------------
type string = System.String

Full name: Microsoft.FSharp.Core.string
val drawBlob : ctx:CanvasRenderingContext2D -> canvas:HTMLCanvasElement -> blob:Blob -> unit

Full name: Ozmo.drawBlob
val blob : Blob
abstract member CanvasRenderingContext2D.beginPath : unit -> unit
abstract member CanvasRenderingContext2D.arc : x:float * y:float * radius:float * startAngle:float * endAngle:float * ?anticlockwise:bool -> unit
namespace System
type Math =
  static val PI : float
  static val E : float
  static member Abs : value:sbyte -> sbyte + 6 overloads
  static member Acos : d:float -> float
  static member Asin : d:float -> float
  static member Atan : d:float -> float
  static member Atan2 : y:float * x:float -> float
  static member BigMul : a:int * b:int -> int64
  static member Ceiling : d:decimal -> decimal + 1 overload
  static member Cos : d:float -> float
  ...

Full name: System.Math
field System.Math.PI = 3.14159265359
abstract member CanvasRenderingContext2D.fill : ?fillRule:string -> unit
property CanvasRenderingContext2D.lineWidth: float
property CanvasRenderingContext2D.strokeStyle: U3<string,CanvasGradient,CanvasPattern>
abstract member CanvasRenderingContext2D.stroke : unit -> unit
val direct : dx:int * dy:'a -> blob:Blob -> Blob

Full name: Ozmo.direct


 Apply key effects on Player's blob - changes X speed
val dx : int
val dy : 'a
val gravity : blob:Blob -> Blob

Full name: Ozmo.gravity


 Apply gravity on falling blobs - gets faster every step
val bounce : blob:Blob -> Blob

Full name: Ozmo.bounce


 Bounde Player's blob off the wall if it hits it
val n : float
val move : blob:Blob -> Blob

Full name: Ozmo.move


 Move blob by one step - adds X and Y
 velocities to the X and Y coordinates
val max : e1:'T -> e2:'T -> 'T (requires comparison)

Full name: Microsoft.FSharp.Core.Operators.max
val step : int * 'a -> blob:Blob -> Blob

Full name: Ozmo.step


 Apply step on Player's blob. Composes above functions.
val dir : int * 'a
val collide : a:Blob -> b:Blob -> bool

Full name: Ozmo.collide


 Check whether two blobs collide
val a : Blob
val b : Blob
val dx : float
val dy : float
val dist : float
val sqrt : value:'T -> 'U (requires member Sqrt)

Full name: Microsoft.FSharp.Core.Operators.sqrt
val abs : value:'T -> 'T (requires member Abs)

Full name: Microsoft.FSharp.Core.Operators.abs
val absorb : blob:Blob -> drops:Blob list -> Blob list

Full name: Ozmo.absorb


 Remove all falling blobs that hit Player's blob
val drops : Blob list
type 'T list = List<'T>

Full name: Microsoft.FSharp.Collections.list<_>
Multiple items
module List

from Microsoft.FSharp.Collections

--------------------
type List<'T> =
  | ( [] )
  | ( :: ) of Head: 'T * Tail: 'T list
  interface IEnumerable
  interface IEnumerable<'T>
  member GetSlice : startIndex:int option * endIndex:int option -> 'T list
  member Head : 'T
  member IsEmpty : bool
  member Item : index:int -> 'T with get
  member Length : int
  member Tail : 'T list
  static member Cons : head:'T * tail:'T list -> 'T list
  static member Empty : 'T list

Full name: Microsoft.FSharp.Collections.List<_>
val filter : predicate:('T -> bool) -> list:'T list -> 'T list

Full name: Microsoft.FSharp.Collections.List.filter
val drop : Blob
val not : value:bool -> bool

Full name: Microsoft.FSharp.Core.Operators.not
val grow : string

Full name: Ozmo.grow
val shrink : string

Full name: Ozmo.shrink
val newDrop : color:string -> Blob

Full name: Ozmo.newDrop
val color : string
val newGrow : unit -> Blob

Full name: Ozmo.newGrow
val newShrink : unit -> Blob

Full name: Ozmo.newShrink
val updateDrops : drops:Blob list -> countdown:int -> Blob list * int

Full name: Ozmo.updateDrops


 Update drops and countdown in each step
val countdown : int
val floor : value:'T -> 'T (requires member Floor)

Full name: Microsoft.FSharp.Core.Operators.floor
val countDrops : drops:Blob list -> int * int

Full name: Ozmo.countDrops


 Count growing and shrinking drops in the list
val count : (string -> int)
val length : list:'T list -> int

Full name: Microsoft.FSharp.Collections.List.length
val game : unit -> Async<'a>

Full name: Ozmo.game


 Starts a new game
val async : AsyncBuilder

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.async
val update : blob:Blob -> drops:Blob list -> countdown:int -> Async<'a>

Full name: Ozmo.update


 Keeps current state for Player's blob, falling
 drops and the countdown since last drop was generated
val completed : unit -> Async<'a>

Full name: Ozmo.completed


 Displays message and sleeps for 10 sec
Multiple items
type Async
static member AsBeginEnd : computation:('Arg -> Async<'T>) -> ('Arg * AsyncCallback * obj -> IAsyncResult) * (IAsyncResult -> 'T) * (IAsyncResult -> unit)
static member AwaitEvent : event:IEvent<'Del,'T> * ?cancelAction:(unit -> unit) -> Async<'T> (requires delegate and 'Del :> Delegate)
static member AwaitIAsyncResult : iar:IAsyncResult * ?millisecondsTimeout:int -> Async<bool>
static member AwaitTask : task:Task -> Async<unit>
static member AwaitTask : task:Task<'T> -> Async<'T>
static member AwaitWaitHandle : waitHandle:WaitHandle * ?millisecondsTimeout:int -> Async<bool>
static member CancelDefaultToken : unit -> unit
static member Catch : computation:Async<'T> -> Async<Choice<'T,exn>>
static member Choice : computations:seq<Async<'T option>> -> Async<'T option>
static member FromBeginEnd : beginAction:(AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg:'Arg1 * beginAction:('Arg1 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg1:'Arg1 * arg2:'Arg2 * beginAction:('Arg1 * 'Arg2 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg1:'Arg1 * arg2:'Arg2 * arg3:'Arg3 * beginAction:('Arg1 * 'Arg2 * 'Arg3 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromContinuations : callback:(('T -> unit) * (exn -> unit) * (OperationCanceledException -> unit) -> unit) -> Async<'T>
static member Ignore : computation:Async<'T> -> Async<unit>
static member OnCancel : interruption:(unit -> unit) -> Async<IDisposable>
static member Parallel : computations:seq<Async<'T>> -> Async<'T []>
static member RunSynchronously : computation:Async<'T> * ?timeout:int * ?cancellationToken:CancellationToken -> 'T
static member Sleep : millisecondsDueTime:int -> Async<unit>
static member Start : computation:Async<unit> * ?cancellationToken:CancellationToken -> unit
static member StartAsTask : computation:Async<'T> * ?taskCreationOptions:TaskCreationOptions * ?cancellationToken:CancellationToken -> Task<'T>
static member StartChild : computation:Async<'T> * ?millisecondsTimeout:int -> Async<Async<'T>>
static member StartChildAsTask : computation:Async<'T> * ?taskCreationOptions:TaskCreationOptions -> Async<Task<'T>>
static member StartImmediate : computation:Async<unit> * ?cancellationToken:CancellationToken -> unit
static member StartWithContinuations : computation:Async<'T> * continuation:('T -> unit) * exceptionContinuation:(exn -> unit) * cancellationContinuation:(OperationCanceledException -> unit) * ?cancellationToken:CancellationToken -> unit
static member SwitchToContext : syncContext:SynchronizationContext -> Async<unit>
static member SwitchToNewThread : unit -> Async<unit>
static member SwitchToThreadPool : unit -> Async<unit>
static member TryCancelled : computation:Async<'T> * compensation:(OperationCanceledException -> unit) -> Async<'T>
static member CancellationToken : Async<CancellationToken>
static member DefaultCancellationToken : CancellationToken

Full name: Microsoft.FSharp.Control.Async

--------------------
type Async<'T>

Full name: Microsoft.FSharp.Control.Async<_>
static member Async.Sleep : millisecondsDueTime:int -> Async<unit>
val beforeGrow : int
val beforeShrink : int
val map : mapping:('T -> 'U) -> list:'T list -> 'U list

Full name: Microsoft.FSharp.Collections.List.map
val afterGrow : int
val afterShrink : int
val radius : float
static member Async.StartImmediate : computation:Async<unit> * ?cancellationToken:System.Threading.CancellationToken -> unit
Fork me on GitHub