MVC + Agents (MailboxProcessor) + Immutable Model

All

Code Snippet:

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
open System
open System.Drawing
open System.Windows.Forms

type Agent<'a> = MailboxProcessor<'a>

module Utils =
  let doEvents () = Application.DoEvents()

module Model =
  type ('a) Model = { state: 'a; events: 'a list }
  // Add functions here to load/persist state and events

module View =
  // Main form
  let title = @"Time Traveling Debugger"
  let width,height = 1024,768
  let form = 
    new Form(
      Visible=true, TopMost=true, ClientSize=Size(width,height),
      MaximizeBox=false, FormBorderStyle=FormBorderStyle.FixedDialog,Text=title)
  let canvas = new Rectangle(0, 0, width, height)
  form.Paint.Add(fun e -> e.Graphics.FillRectangle(Brushes.Black, canvas))

  // Time Traveling Debugger
  let debug = new TrackBar()
  debug.Location <- Point(0,height - 100)
  debug.TickStyle <- TickStyle.Both
  debug.AutoSize <- false
  debug.Width <- width
  debug.Height <- 100
  debug.Minimum <- 0
  debug.Maximum <- 0
  debug.Value <- 0
  form.Controls.Add(debug)

  // Time Traveling Debugger info text
  let debugText = new Label()
  debugText.Location <- Point(0, 50)
  debugText.Width <- width
  debugText.TextAlign <- ContentAlignment.MiddleCenter
  debug.Controls.Add(debugText)

  // Update functions "hardcoded" to above formular and formular controls
  let updateTitle point = form.Text <- sprintf "%s: %A" title point
  let updateCanvas p1 p2 =
    let prev = new Rectangle(fst p1, snd p1, 30, 30)
    form.Paint.Add(fun e -> e.Graphics.FillRectangle(Brushes.Black, prev))
    let next = new Rectangle(fst p2, snd p2, 30, 30)
    form.Paint.Add(fun e -> e.Graphics.FillRectangle(Brushes.LimeGreen, next))
    form.Invalidate(next)
    form.Invalidate(prev)
  let updateDebug n = 
    debug.Maximum <- n
    debug.Value <- n
  let updateDebugText n s p = 
    debugText.Text <- sprintf "Nr. events: %i\nDebug step: %i %A" (n+1) (s+1) p

module Controller =
  open Model
  open View

  type Action = | Event of Event | Debug of Debug
  and Event = int * int
  and Debug = int

  // By using Agents, the model stays inmutable
  let agent = Agent.Start(fun inbox ->
    let rec loop model = async {
      let! msg = inbox.Receive()
      match msg with
      | Event(point) ->
        let model' = { model with state = point; events = point::model.events }
        let n = (model'.events |> List.length) - 1
        updateTitle model'.state
        updateCanvas model.state model'.state
        updateDebug n
        updateDebugText n n point
        return! loop model'
      | Debug(index) ->
        let n = (model.events |> List.length) - 1
        let point = model.events |> List.skip (n-index) |> List.head
        let model' = { model with state = point }
        updateTitle model'.state
        updateCanvas model.state model'.state
        updateDebugText n index point
        return! loop model' }
    loop { Model.state = (0,0); events = [] })

  // Hook-up model events
  form.MouseClick
  |> Event.add (fun e -> agent.Post (Action.Event(e.X,e.Y)))

  // Hook-up Time Traveling Debugger events
  debug.Scroll
  |> Event.add(fun _ -> agent.Post (Action.Debug(debug.Value)))

// Program event loop, not to use with F# Interactive (FsiAnyCpu)
open Utils
open View

let rec main = function | true -> doEvents(); main form.Created | false -> ()

main form.Created

Code output:

All

References: