Mick Duprez
Mick Duprez

Reputation: 125

Learning F# Through Game Dev chapter 3 error

I am going through the Learning F# Through Game Development book and in chapter 3 I am getting a TypeInitializationException with the following code:

// edit: missing definitions, thanks John
let earth_mass  = 5.97e24<kg> // defined in other file that is ref'd in ok
let moon_mass = 7.35e22<kg>

let lerp (x:float<'T>) (y:float<'T>) (a:float) = (x * a) + (y * (1.0 - a))
// inside list comprehension for loop -
let m = (lerp earth_mass moon_mass (rand.NextDouble())) * 1.0e-4 // error!!

I've downloaded the authors source to see if I missed something but it yields the same error. The exception message gives little clue to what the issue is either (to me at least).

Here is the whole code for this chapter for reference:

namespace Games

module Chapter3 =
open System
open System.Threading
open Games.Math

type Asteroid =
    {
    Position : Vector2<m>
    Velocity : Vector2<m/s>
    Mass     : float<kg>
    Name     : string
    }

let dt = 60.0<s>
let G = 6.67e-11<m^3 * kg^-1 * s^-2>

let earth_radius = 6.37e6<m>
let field_size = earth_radius * 60.0
let max_velocity = 2.3e4<m/s>
let earth_mass  = 5.97e24<kg>
let moon_mass = 7.35e22<kg>

let create_field num_asteroids =
    let lerp (x:float<'T>) (y:float<'T>) (a:float) = x * a + y * (1.0 - a)
    let rand = Random()
    [
    for i = 1 to num_asteroids do
        let m = (lerp earth_mass moon_mass (rand.NextDouble())) * 1.0e-4
        let x = lerp 0.0<m> field_size (rand.NextDouble())
        let y = lerp 0.0<m> field_size (rand.NextDouble())
        let vx = max_velocity * (rand.NextDouble() * 2.0 - 1.0) * 0.1
        let vy = max_velocity * (rand.NextDouble() * 2.0 - 1.0) * 0.1

        yield
            {
            Position = { X = x; Y = y}
            Velocity = { X = vx; Y = vy}
            Mass = m
            Name = "a"
            }
    ]

let f0 = create_field 20

let clamp (p:Vector2<_>, v:Vector2<_>) = 
    let p,v =
        if p.X < 0.0<_> then
            {p with X = 0.0<_>}, {v with X = -v.X}
        else
            p,v
    let p,v =
        if p.X > field_size then
            {p with X = field_size}, {v with X = -v.X}
        else
            p,v
    let p,v =
        if p.Y < 0.0<_> then
            {p with Y = 0.0<_>}, {v with Y = -v.Y}
        else
            p,v
    let p,v =
        if p.Y > field_size then
            {p with Y = field_size}, {v with Y = -v.Y}
        else
            p,v
    p,v

let force (a:Asteroid, a':Asteroid) =
    let dir = a'.Position - a.Position
    let dist = dir.Length + 1.0<m>
    G * a.Mass * a'.Mass * dir/(dist * dist * dist)

let simulation_step (asteroids:Asteroid list) =
    [
        for a in asteroids do
            let forces =
                [
                    for a' in asteroids do
                        if a' <> a then
                            yield force(a, a')
                ]    
            let F = List.sum forces
            let p', v' = clamp(a.Position, a.Velocity)
            yield
                {
                    a with
                        Position = p' + dt * v'
                        Velocity = v' + dt * F/a.Mass
                }
    ]

let print_scene (asteroids:Asteroid list) =
    do Console.Clear()
    for i = 0 to 79 do
        Console.SetCursorPosition(i, 0)
        Console.Write("*")
        Console.SetCursorPosition(i, 23)
        Console.Write("*")
    for j = 0 to 23 do
        Console.SetCursorPosition(0, j)
        Console.Write("*")
        Console.SetCursorPosition(79, j)
        Console.Write("*")
    let set_cursor_on_body b =
        Console.SetCursorPosition(
            ((b.Position.X/4.0e8<m>) * 78.0 + 1.0) |> int,
            ((b.Position.Y/4.0e8<m>) * 23.0 + 1.0) |> int)
    for a in asteroids do
        do set_cursor_on_body a
        do Console.Write(a.Name)
    do Thread.Sleep(100)

let simulation() =
    let rec simulation m =
        do print_scene m
        let m' = simulation_step m
        do simulation m'
    do simulation f0

Code for Math module:

namespace Games

module Math =
[<Measure>]
type m //metres

[<Measure>]
type kg //kilogram

[<Measure>]
type s // seconds

[<Measure>]
type N = kg*m/s^2 //Newtons

type Vector2<[<Measure>]'T> =
    {
        X : float<'T>
        Y : float<'T>
    }

    static member Zero : Vector2<'T> =
        { X = 0.0<_>; Y = 0.0<_> }

    static member (+) (v1:Vector2<'T>, v2:Vector2<'T>) : Vector2<'T> =
        { X = v1.X + v2.X; Y = v1.Y + v2.Y }

    static member (+) (v:Vector2<'T>, k:float<'T>) : Vector2<'T> =
        { X = v.X + k; Y = v.Y + k }

    static member (+) (k:float<'T>, v:Vector2<'T>) : Vector2<'T> = v + k

    static member (~-) (v:Vector2<'T>) : Vector2<'T> =
        { X = -v.X; Y = -v.Y }

    static member (-) (v1:Vector2<'T>, v2:Vector2<'T>) : Vector2<'T> =
        v1 + (-v2)

    static member (-) (v:Vector2<'T>, k:float<'T>) : Vector2<'T> =
        v + (-k)

    static member (-) (k:float<'T>, v:Vector2<'T>) : Vector2<'T> =
        k + (-v)

    static member (*) (v1:Vector2<'a>, v2:Vector2<'b>) : Vector2<'a * 'b> =
        { X = v1.X * v2.X; Y = v1.Y * v2.Y }

    static member (*) (v:Vector2<'a>, f:float<'b>) : Vector2<'a * 'b> =
        { X = v.X * f; Y = v.Y * f }

    static member (*) (f:float<'b>, v:Vector2<'a>) : Vector2<'b * 'a> =
        { X = f * v.X; Y = f * v.Y }

    static member (/) (v:Vector2<'a>, f:float<'b>) : Vector2<'a / 'b> =
        v * (1.0/f)

    member this.Length : float<'a> =
        sqrt((this.X * this.X + this.Y * this.Y))

    static member Distance(v1:Vector2<'T>, v2:Vector2<'T>) =
        (v1 - v2).Length

    static member Normalize(v:Vector2<'T>) : Vector2<1> = 
        v / v.Length

Code to run example:

[<EntryPoint>]
let main argv = 
    Games.Chapter3.simulation()
    0 // return an integer exit code

Could someone please explain what is going on, using VS2013 and F# 3.1/.Net4.5

thank you.

Mick

Upvotes: 2

Views: 291

Answers (1)

latkin
latkin

Reputation: 16792

It believe you are running into this F# compiler bug. It's present in F# 3.1.2 and earlier, but has since been fixed, so in 4.0 and later this will work.

Per the issue discussion, in F# 3.0 and earlier the bug existed, but didn't always cause a runtime crash. Looking at the book's sample code project, I'm guessing the author wrote these samples using F# 3.0 or earlier, and did not realize they were triggering the bug.

Workarounds:

  • Compile in Release mode (with optimizations ON), and codegen should be correct (assuming this is really the same issue)
  • Try using an F# 4.0 pre-release (either via VS 2015 CTPs or grab just the F# bits from here)

Upvotes: 1

Related Questions