Something went wrong. Try again.
Mibo is an F# game framework with two interchangeable backends — raylib-cs and MonoGame angelmunoz.github.io/Mibo
raylib-cs framework signals dotnet fsharp monogame raylib mvu gamedev
Something went wrong. Try again.
6.6 kB · 156 lines
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157namespace Mibo.Elmish
open Microsoft.Xna.Frameworkopen Microsoft.Xna.Framework.Graphicsopen Mibo.Input
// ─────────────────────────────────────────────────────────────────────────────// MonoGame-specific Program wrapper and builder extensions.//// The backend-neutral Program builder (mkProgram, withConfig, withRenderer,// withTick, withFixedStep, withDispatchMode, withSubscription, withAssets,// withAssetsBasePath, withInput, withServiceRegistration) lives in Mibo.Core.//// MonoGameProgram wraps a Core Program and adds a slot for device-level// configuration callbacks (Game * GraphicsDeviceManager -> unit) that the// Core type cannot hold without leaking MonoGame types. The host (MiboGame)// runs these callbacks in its constructor, after the Core GameConfig but// before Initialize, so settings like GraphicsProfile, vsync, and fullscreen// take effect at device-creation time.// ─────────────────────────────────────────────────────────────────────────────
/// <summary>/// MonoGame-specific wrapper around a Core <see cref="T:Mibo.Elmish.Program`2"/>,/// carrying device-level configuration that the backend-neutral Core type cannot hold./// </summary>/// <remarks>/// Build one with <see cref="M:Mibo.Elmish.MonoGameProgram.ofProgram"/>, add/// MonoGame-specific configuration with/// <see cref="M:Mibo.Elmish.MonoGameProgram.withConfig"/> and/or/// <see cref="M:Mibo.Elmish.MonoGameProgram.withInputMapper"/>, then pass the result/// to <see cref="T:Mibo.Elmish.MiboGame`2"/>./// </remarks>/// <example>/// <code>/// let program =/// Program.mkProgram init update/// |> Program.withConfig(fun cfg ->/// { cfg with Width = 1280; Height = 720; Title = "My Game" })/// |> Program.withInput/// |> Program.withTick Tick////// let mgProgram =/// program/// |> MonoGameProgram.ofProgram/// |> MonoGameProgram.withConfig(fun (game, graphics) ->/// graphics.SynchronizeWithVerticalRetrace <- false)////// use game = new MiboGame<_, _>(mgProgram)/// game.Run()/// </code>/// </example>type MonoGameProgram<'Model, 'Msg> = { /// <summary>The backend-neutral Core program.</summary> Program: Program<'Model, 'Msg>
/// <summary> /// Device-level configuration callbacks invoked in the /// <see cref="T:Mibo.Elmish.MiboGame`2"/> constructor, after the Core /// <see cref="F:Mibo.Elmish.Program.Config"/> callbacks and before /// <c>Initialize</c>/<c>GraphicsDevice</c> creation. /// </summary> DeviceConfig: (Game * GraphicsDeviceManager -> unit) list}
/// <summary>MonoGame-specific program builder extensions.</summary>module MonoGameProgram =
/// <summary> /// Wraps a Core <see cref="T:Mibo.Elmish.Program`2"/> into a /// <see cref="T:Mibo.Elmish.MonoGameProgram`2"/> with no device-level /// configuration. /// </summary> let ofProgram (program: Program<'Model, 'Msg>) : MonoGameProgram<'Model, 'Msg> = { Program = program; DeviceConfig = [] }
/// <summary> /// Adds a device-level configuration callback that receives the <c>Game</c> /// instance and <c>GraphicsDeviceManager</c> for low-level setup. /// </summary> /// <remarks> /// <para>Invoked in the <see cref="T:Mibo.Elmish.MiboGame`2"/> constructor, /// after the Core <see cref="M:Mibo.Elmish.Program.withConfig"/> callbacks /// and before <c>Initialize</c> / <c>GraphicsDevice</c> creation — so /// settings like <c>GraphicsProfile</c> and /// <c>PreferredBackBufferWidth/Height</c> take effect.</para> /// <para>Use this for properties that require direct /// <c>GraphicsDeviceManager</c>/<c>Game</c> access: /// <c>GraphicsProfile</c>, <c>SynchronizeWithVerticalRetrace</c> (vsync), /// <c>IsFullScreen</c>, <c>HardwareModeSwitch</c>, /// <c>Window.AllowUserResizing</c>, <c>Content.RootDirectory</c>, etc.</para> /// </remarks> /// <example> /// <code> /// program /// |> MonoGameProgram.ofProgram /// |> MonoGameProgram.withConfig(fun (game, graphics) -> /// graphics.GraphicsProfile <- GraphicsProfile.HiDef /// graphics.SynchronizeWithVerticalRetrace <- false /// game.IsMouseVisible <- true) /// </code> /// </example> let withConfig (configure: Game * GraphicsDeviceManager -> unit) (program: MonoGameProgram<'Model, 'Msg>) : MonoGameProgram<'Model, 'Msg> = { program with DeviceConfig = configure :: program.DeviceConfig }
/// <summary> /// Configures the game to register an <see cref="T:Mibo.Input.IInputMapper`1"/> service /// backed by MonoGame's polling API. /// </summary> /// <remarks> /// <para>This registers <see cref="T:Mibo.Input.IInput"/> automatically (equivalent to /// <see cref="M:Mibo.Elmish.Program.withInput"/>).</para> /// <para>The mapper is registered as a service via a /// <see cref="F:Mibo.Elmish.Program.ServiceRegistrations"/> callback that /// <c>MiboGame</c> runs before <c>Init</c>, so the Core Program type never /// references a backend factory.</para> /// <para>The service is registered, not driven: nothing polls /// <c>Update()</c> automatically — call it yourself each frame if you read /// <c>CurrentState</c>, or prefer the subscription path below, which the /// input deltas drive on their own.</para> /// <para>If you want to stay fully "Elmish" (no service access), consider using /// <see cref="M:Mibo.Input.InputMapper.subscribe"/> instead and handle a single message /// (adaptive programs: <see cref="M:Mibo.Input.InputMapper.subscribeAdaptive"/>).</para> /// </remarks> /// <example> /// <code> /// program /// |> MonoGameProgram.ofProgram /// |> MonoGameProgram.withInputMapper inputMap /// </code> /// </example> let withInputMapper<'Model, 'Msg, 'Action when 'Action: comparison> (initialMap: InputMap<'Action>) (program: MonoGameProgram<'Model, 'Msg>) : MonoGameProgram<'Model, 'Msg> = let coreProgram = program.Program |> Program.withInput |> Program.withServiceRegistration(fun ctx -> let mapper = InputMapper.createService initialMap GameContext.register<IInputMapper<'Action>> mapper ctx)
{ program with Program = { coreProgram with HasInputMapper = true } }