0

I am having a go at building a starcraft2 AI in .Net and I want to display a window with some real time feature map data. The AI runs from a console application, and I would like to keep it that way. I would like to have my code spit out some bitmap data to be displayed in a window, as the AI does its thing in real time. However, from this answer, it would seem that running a form from a console application would block the console thread.

How can I run the form, and regularly pass a bitmap to it, from the console application? I am doing this in F# but am happy to see answers based in C# or VB.Net as I can just translate it as needed.

I don't want to try running it as a Windows application to display the form then having my AI running from there, because at some point I might want to compete my AI on the ladder and I am not sure if a windows forms app would be supported.

I suppose I could have my console app publish the data via zero mq or something, and then any process that happens to be listening could just display it, but that seems a bit complex for a simple requirement. I will consider this to be the answer if no better (easier) solution is presented.

Chechy Levas
  • 2,206
  • 1
  • 13
  • 28
  • Have you considered using an F# `MailboxProcessor` to send messages from the console application to the Form running on another thread? If you have to use a separate process, maybe just make a little WCF service. – Aaron M. Eshbach Jul 27 '18 at 14:11
  • I am busy trying that now, but it is not entirely clear to me how to do it yet. If I get it right I will post it as an answer. – Chechy Levas Jul 27 '18 at 14:13

1 Answers1

1

Here's a working example using an F# MailboxProcessor and hosting the form on a separate thread. This uses a text-box instead of an image, but it's the same idea.

module ConsoleApp 

open System
open System.Windows.Forms

type WinForm () as form =
    inherit Form ()
    let textBox = new TextBox()
    do form.Controls.Add(textBox)

    member __.SetText text = textBox.Text <- text

type Message = SetText of string
type UpdateText = delegate of unit -> unit

let form = new WinForm()    

let agent =
    MailboxProcessor.Start
    <| fun inbox ->
        let rec loop () =
            async {
                let! (SetText text) = inbox.Receive()
                form.Invoke(UpdateText(fun () -> form.SetText(text))) |> ignore
                return! loop ()
            }
        loop ()

[<EntryPoint>]
let main argv =
    async { form.ShowDialog() |> ignore } |> Async.Start
    let rec loop () =
        printf "Enter Text: "
        match Console.ReadLine() with
        | "exit" -> ()
        | text -> agent.Post <| SetText text; loop()
    loop ()
    0

Note, it's important to display the form with ShowDialog, or the thread hosting the form will go back to the thread-pool and the form will become unresponsive.

Aaron M. Eshbach
  • 6,380
  • 12
  • 22
  • So I don't need to worry about synchronization contexts? As in 'CaptureCurrent' in the thread I show dialog from, and then 'SwitchToContext' when I set the value of some control? – Chechy Levas Jul 27 '18 at 14:37
  • @ChechyLevas Fair point, I didn't think about that because it's been a while since I did any GUI work, and the original code worked when I tried it. I updated the answer to use `form.Invoke` when setting the text, I think that should take care of any synchronization context issues. – Aaron M. Eshbach Jul 27 '18 at 14:43