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#.
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)
|
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()
|
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 )
|
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
|
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
new : macro:string -> EmitAttribute
Full name: Fable.Core.EmitAttribute
--------------------
new : macro: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 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