----------------------------------------------------------------------------- {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE CPP #-} ----------------------------------------------------------------------------- -- | -- Module : Miso.Native -- Copyright : (C) 2016-2026 David M. Johnson -- License : BSD3-style (see the file LICENSE) -- Maintainer : David M. Johnson <code@dmj.io> -- Stability : experimental -- Portability : non-portable -- -- = miso native 📱 -- -- "Miso.Native" targets __native mobile devices__ by driving the -- [Lynx](https://lynxjs.org) runtime instead of the browser DOM. The same -- [MVU](https://elm-lang.org) programming model, 'Component' API, event -- delegation and virtual-DOM diffing you use on the web ("Miso") carry over -- unchanged — only the element vocabulary differs ('Miso.Native.Element.view_', -- 'Miso.Native.Element.text_', … instead of 'Miso.Html.Element.div_' \/ -- 'Miso.Html.Element.span_') and rendering is performed by Lynx's -- [element PAPI](https://lynxjs.org/api/engine/element-api) rather than by -- mutating a browser DOM. -- -- This module is the native analog of the 'Miso.miso' \/ 'Miso.startApp' -- entrypoints: 'native' (and 'nativeWithContext') boot a root 'Component' onto -- the Lynx runtime. -- -- == Enabling native -- -- The native backend is gated behind the @native@ /cabal flag/. It must be -- enabled to bring "Miso.Native" and the @Miso.Native.*@ element \/ event \/ FFI -- modules into scope (build with @-fnative@). Web \/ WASM builds are unaffected — -- all cross-thread machinery lives behind the @NATIVE@ CPP guard. -- -- = The dual-thread architecture -- -- Lynx runs your application across __two threads__, and miso maps onto both: -- -- * __BTS__ — the /background thread/ (\"background thread script\"). This is -- where your application /logic/ lives. Everything runs here __by default__: -- the 'Miso.Types.update' function, event handling, 'Effect' scheduling and -- /all/ virtual-DOM diffing. -- -- * __MTS__ — the /main thread/ (\"main thread script\"). This thread owns the -- actual element tree and /rendering/. It is where the pixels land. It is also -- available as a low-latency escape hatch for performance-critical event -- handling (see [Main-thread events](#g:mainthread) below). -- -- The __same Haskell bundle runs on both threads__; the native runtime -- (@ts\/miso-native.ts@) selects the BTS or MTS drawing context per-thread from a -- global flag, so there is no renderer to register — 'native' starts the app -- directly. -- -- The guiding principle: __everything originates on the BTS__. The MTS is a -- rendering surface that the BTS drives across the thread boundary. -- -- == Knowing which thread you are on -- -- Lynx builds the bundle with [rspeedy](https://lynxjs.org) — its Rust-based -- tooling — which compiles the sources /twice/, once per thread, inlining a -- __compile-time constant__ (@__BACKGROUND__@) that distinguishes the two. That -- constant surfaces in Haskell as three top-level 'Bool's in "Miso.Runtime": -- -- * @mts@ — 'True' when this execution context is the Lynx /main/ thread. -- * @bts@ — 'True' when this context is the Lynx /background/ thread. -- * @web@ — 'True' for a plain web \/ WASM build (neither Lynx thread). -- -- Exactly one is 'True', and the value is invariant for the lifetime of a JS -- context, so the runtime computes it once and caches it. Runtime code branches -- on @mts@ \/ @bts@ to decide where work runs (e.g. the scheduler suppresses the -- paint step on the MTS, which keeps only a read-only @model@ replica). -- -- == What crosses the thread boundary, and how -- -- Because logic (BTS) and rendering (MTS) live on different threads, miso -- synchronizes them by shipping messages across the boundary. This is largely -- invisible, but understanding it explains the API constraints below. -- -- * __Initial draw__ — The very first 'Draw' happens __on the MTS itself__, and -- it does __not__ rely on the BTS diffing a tree and transferring patches -- across the boundary. The root 'Component' is booted from a 'StaticPtr' (via -- 'native' \/ 'nativeWithContext'), so the MTS reconstructs it from the -- pointer's 'GHC.StaticPtr.StaticKey' alone and renders the first frame -- locally (Lynx's instant first frame). Only /after/ this initial draw does the -- cross-thread patch protocol take over: __every subsequent diff runs on the -- BTS and ships patches to the MTS__ to apply. -- -- * __Subsequent component mounts__ — When the BTS 'view' mounts a child -- 'Component', that mount is synchronized to the MTS __asynchronously__ using -- /static mounting/: the child is wrapped in a @static@ pointer -- (@-XStaticPointers@) so only its 'GHC.StaticPtr.StaticKey' — not a closure — -- needs to cross the boundary. The MTS dereferences the key to rebuild the -- component locally. See 'Miso.Types.vcomp' \/ 'Miso.Types.mountStatic_'. -- -- * __State synchronization__ — The BTS owns the shared @model@ and ships it to -- the MTS as it changes (JSON-serialized, hence the @ToJSON@ \/ @FromJSON@ -- constraints on native mounting combinators), so main-thread @*MainWith@ -- handlers observe an eventually-consistent copy. A child's initial @props@ ride -- the /static mount/ payload — the @static@ pointer carries the /constructor/ -- and the @props@ value is shipped separately, so it may depend on the parent -- @model@. But @props@ and the global @context@ are __not__ re-synced on later -- changes: after the first frame they stay background-thread-only (matching -- ReactLynx — see [Main-thread events](#g:mainthread)). -- -- * __Events__ — Events raised on the MTS are, by default, forwarded to the BTS -- where 'update' runs (see below). Cross-thread handlers are carried as an -- 'Miso.Types.EventHandler', embedded with 'Miso.Types.event' @. static (…)@ so -- the peer thread can rebuild the handler from its 'GHC.StaticPtr.StaticKey'. -- -- == First-frame rendering (instant first frame) -- -- The MTS painting frame one itself (the __Initial draw__ above) is Lynx's -- /instant first frame/: the user sees UI without waiting for a background render -- and patch round-trip. Meanwhile the BTS boots the /same/ root and builds the -- identical virtual-DOM tree in lockstep — with __deterministic @nodeId@ -- parity__, so both threads address the same elements — but __suppresses its own -- create-patches__ for that first frame, since the MTS already painted them. A -- single global @initialDraw@ latch governs this on both threads; 'native' \/ -- 'nativeWithContext' clears it once the whole root mount has finished. -- -- After that handover the responsibilities are fixed, mirroring ReactLynx: the -- __BTS is the sole diff \/ paint authority__ — it runs 'update', diffs, and ships -- patches — while the __MTS only applies those patches__ (and runs main-thread -- scripts \/ handlers). The MTS never diffs or repaints from the scheduler again; -- this is why the shared @model@ is BTS-owned and why nothing you do on the MTS -- should try to redraw declaratively. -- -- = Static mounting -- -- Because component constructors, event handlers and effects may need to be -- reconstructed on the /other/ thread, native miso threads them across the -- boundary as @static@ pointers rather than closures. This requires the -- @-XStaticPointers@ language extension. -- -- The root component is mounted with 'Miso.Types.mountStatic_' wrapped in -- @static@: -- -- @ -- {-# LANGUAGE StaticPointers #-} -- ----------------------------------------------------------------------------- -- module Main where -- ----------------------------------------------------------------------------- -- import "Miso" -- import "Miso.Native" -- ----------------------------------------------------------------------------- -- main :: 'IO' () -- main = 'native' 'nativeEvents' (static ('Miso.Types.mountStatic_' app)) -- @ -- -- Child components are embedded in a 'view' the same way, with 'Miso.Types.vcomp': -- -- @ -- view _ _ _ = view_ [] [ 'Miso.Types.vcomp' () (static ('Miso.Types.mountStatic_' childComponent)) ] -- @ -- -- __Static-pointer limitation.__ A @static@ form may only close over -- /top-level, closed/ bindings — it cannot capture local variables. This is why -- component constructors and main-thread handlers are supplied as references to -- top-level definitions, with any runtime data (props, decoded event payloads) -- shipped separately as serialized values rather than captured in a closure. -- -- = Effects: choosing a thread -- -- Because an 'IO' closure can't cross the thread boundary (only JSON-serialized -- @action@s can), cross-thread work is expressed as /dispatching an action/ to -- the thread that should handle it. Two combinators do this: -- -- * 'Miso.Effect.runOnBG' @action@ — run @action@'s 'update' on the -- __background__ thread (BTS). Used by a main-thread event handler that needs -- to change shared state, since the BTS solely owns the @model@. -- * 'Miso.Effect.runOnMain' @action@ — run @action@'s 'update' on the __main__ -- thread (MTS). Used by a BTS effect that needs an imperative main-thread -- operation (see "Miso.Native.MainThread"). -- -- Each ships only the given @action@ to the target thread (or dispatches it -- locally when already there), where its 'update' runs exactly once. Sibling -- effects in the current 'update' are unaffected, and nothing is -- double-executed. Off the native runtime both are an ordinary local dispatch, -- equivalent to 'Miso.Effect.issue'. -- -- = Subscriptions and threads -- -- A t'Miso.Effect.Sub' is dynamic — it is just a @'Miso.Effect.Sink' action -> -- IO ()@ run in a forked thread — and a component's subs are started on __every -- thread it mounts on__. So a 'Miso.Effect.Sub' runs on __both the BTS and the -- MTS__ (once each), and each copy dispatches into its own thread's scheduler. -- -- Because a 'Miso.Effect.Sub' is ordinary runtime IO — unlike a @static@ event -- handler, whose thread is fixed at compile time — it selects its own thread at -- runtime with the @mts@ \/ @bts@ 'Bool's. This is the dynamic analog of a -- handler's @*Main@ variant: -- -- @ -- -- background-only: open the socket once, feed the model -- wsSub sink = when bts (websocketConnect \"wss:\/\/…\" sink) -- -- -- main-thread-only: drive an imperative animation -- animSub _ = when mts ('Miso.Native.MainThread.eachFrame' step) -- @ -- -- __Guard anything that must be single-owned.__ Without a @bts@ \/ @mts@ gate a -- stateful sub double-runs — two websocket connections, a timer ticking on both -- threads — so pin such subs to one thread. The no-op fork on the other thread -- returns immediately. -- -- = Main-thread events #mainthread# -- -- __Thread affinity is per-handler, not per-event-name.__ Any given event can be -- handled on /either/ thread; the choice is made at each handler, so the same -- event (say @tap@) may run on the BTS for one element and the MTS for another. -- The __default is the BTS__ — a plain 'Miso.Native.Element.View.Event.onTap' -- handler runs on the background thread. Opting a handler into the MTS is -- explicit (the @*Main@ variants below); nothing runs on the main thread unless -- you ask for it. -- -- By default an event handler runs on the __BTS__: the event is forwarded from -- the MTS, 'update' runs on the BTS, the model changes, and the resulting diff is -- shipped back to the MTS to paint. That round-trip is fine for most -- interactions but adds latency for gesture- and scroll-linked animation. -- -- For those cases, handlers have __@*Main@-suffixed variants__ (e.g. -- 'Miso.Native.Element.View.Event.onTapMain', -- 'Miso.Native.Element.View.Event.onTouchMoveMain') that run __synchronously on -- the MTS__ — no VDOM diff, no patches, no BTS round-trip. Such a handler is -- /imperative/: it mutates the target element directly through the helpers in -- "Miso.Native.MainThread" (e.g. 'Miso.Native.MainThread.setStyleProperty'). The -- @*MainWith@ variants additionally hand the handler the current @model@ and the -- target 'Miso.Types.DOMRef' (@\\event model domRef -> action@). -- -- Because a main-thread handler must be reconstructed on the MTS, it is an -- 'Miso.Types.EventHandler' embedded with 'Miso.Types.event' @. static@ — so -- __main-thread event handlers require @-XStaticPointers@__ (the @static@ keyword -- is how the handler crosses to the MTS by 'GHC.StaticPtr.StaticKey'): -- -- @ -- {-# LANGUAGE StaticPointers #-} -- -- view _ _ _ = -- 'view_' [ 'Miso.Types.event' (static ('Miso.Native.Element.View.Event.onTapMain' HandleTap)) ] [] -- @ -- -- The same @static@ capture limitation applies: an @onTapMain@ handler refers to -- a top-level action \/ function; runtime data reaches the handler via the -- decoded event payload, not a captured closure. -- -- __The generic primitives ('Miso.Event.on' \/ 'Miso.Event.onMain').__ The -- per-element @on*@ \/ @on*Main@ helpers are sugar over two combinators, and the -- /same/ @(eventName, decoder, toAction)@ works with either — that is how one -- event is captured on whichever thread you choose, per handler: -- -- * 'Miso.Event.on' @name decoder toAction@ → a plain 'Miso.Types.Attribute' -- that runs on the __BTS__. No @static@: a background handler is reconstructed -- nowhere else, so it may close over the enclosing 'view'. -- * 'Miso.Event.onMain' @name decoder toAction@ → an 'Miso.Types.EventHandler' -- that runs on the __MTS__, embedded with 'Miso.Types.event' @. static@. -- -- @ -- -- same @tap@ event, one handler per thread: -- view_ [ 'Miso.Event.on' \"tap\" emptyDecoder (\\_ _ _ -> Grow) ] children -- BTS -- view_ [ 'Miso.Types.event' (static ('Miso.Event.onMain' \"tap\" emptyDecoder onTapMain)) ] children -- MTS -- @ -- -- The @Attribute@-versus-@EventHandler@+@static@ split /is/ the mechanism: only -- the main-thread handler has to cross to the MTS by 'GHC.StaticPtr.StaticKey', -- which is why 'Miso.Event.onMain' (and every @*Main@ helper) needs -- @-XStaticPointers@ while 'Miso.Event.on' does not. ('Miso.Event.onMainWithOptions' -- exposes 'Miso.Event.Types.Phase' \/ 'Miso.Event.Types.Options' for the MTS -- variant, mirroring 'Miso.Event.onWithOptions'.) -- -- __Reaching the @model@ (and why it is passed, not captured).__ A static -- main-thread handler /cannot/ close over the @model@, @props@ or @context@ from -- the enclosing 'view' — those are local bindings, which @static@ forbids. So -- rather than capture them, the @*MainWith@ variants __pass the @model@ as an -- argument__ to the handler, giving imperative MTS code the state it needs to -- integrate without a BTS round-trip. Note this is the __main-thread's own copy__ -- of the model: it is populated on the MTS __eventually consistently__ from the -- BTS (the authoritative model still lives on the background thread), so a -- handler may observe a value slightly behind the latest BTS state. -- -- __Props and context are not on the main thread.__ Unlike the @model@, a -- component's @props@ and the app-global @context@ are __not__ mirrored to the -- MTS at all (matching ReactLynx, where React state — and therefore props and -- context — is background-thread-only). They live solely on the BTS; the MTS -- keeps only its boot values, so 'Miso.Effect.getProps' \/ -- 'Miso.Effect.getContext' inside a main-thread handler would read stale data. -- Only the @model@ is hydrated to the MTS (eventually consistently, as above). -- If a main-thread handler needs a prop or context value, fold it into the -- @model@ or carry it in the dispatched action payload — do not read @props@ or -- @context@ on the main thread. This also means less cross-thread traffic: the -- BTS ships a @props@\/@context@ change to the MTS only via the initial 'MOUNT' -- (for @props@), never on every subsequent change. -- -- __Ownership caveat.__ A property you drive imperatively from the MTS must not -- /also/ be written declaratively by the BTS @view@ for the same element: both -- threads write the shared element tree through the same PAPI with no -- arbitration, so one will clobber the other. Keep a single owner per -- @(element, property)@ — typically compositor properties like @transform@ \/ -- @opacity@ that the @view@ leaves alone. -- -- = Main-thread-local state: 'Miso.Native.MainThread.MainThreadRef' -- -- A main-thread handler is imperative and must not write the BTS-owned @model@: -- shared state changes belong on the background thread, so dispatch them with -- 'Miso.Effect.runOnBG'. But gestures and scroll-linked animation often need -- mutable state that lives /only/ on the MTS — the current drag offset, a fling -- velocity, whether a follow loop is active. For that, use a -- 'Miso.Native.MainThread.MainThreadRef', a thin 'Data.IORef.IORef' wrapper for -- main-thread-only state (the analog of ReactLynx's @MainThreadRef@): -- -- @ -- dragRef :: 'Miso.Native.MainThread.MainThreadRef' Double -- dragRef = 'Miso.Native.MainThread.mainThreadRef' 0 -- {-\# NOINLINE dragRef \#-} -- @ -- -- 'Miso.Native.MainThread.mainThreadRef' allocates the underlying cell as a CAF -- via 'System.IO.Unsafe.unsafePerformIO', so __every top-level binding needs its -- own @{-\# NOINLINE \#-}@ pragma__ — otherwise GHC may inline the CAF and split -- the state into independent copies. Reads and writes -- ('Miso.Native.MainThread.readMainThreadRef' \/ -- 'Miso.Native.MainThread.writeMainThreadRef' \/ -- 'Miso.Native.MainThread.modifyMainThreadRef') are ordinary 'Data.IORef.IORef' -- operations — safe without atomics because the MTS is single-threaded — -- and 'Miso.Native.MainThread.modifyMainThreadRef_' takes a -- @'Control.Monad.State.State' a ()@ so you can drive updates with the -- "Miso.Lens" operators (@.=@, @%=@, @+=@, …). -- -- It pairs with 'Miso.Native.MainThread.eachFrame' for a vsync-coalesced -- animation loop: read the latest gesture state from the ref, imperatively paint -- at most once per frame (via 'Miso.Native.MainThread.setStyleProperty' \/ -- 'Miso.Native.MainThread.setStylePropertyTransform'), and stop by returning -- 'False' when the gesture ends. -- -- = Platform APIs and thread restrictions -- -- Mirroring Lynx (/\"not all APIs exist on both threads\"/), miso's native APIs -- are split by thread, and calling one from the wrong thread fails at runtime — -- the type system does not catch it, so guard with @mts@ \/ @bts@ when code may -- run on either thread. Neither module is re-exported here; import it directly. -- -- * __Native modules (BTS-only)__ — "Miso.Native.Module" wraps Lynx's global -- @NativeModules@ (platform capabilities: storage, clipboard, device info, …). -- 'Miso.Native.Module.callNativeModule' invokes a void-returning method and -- 'Miso.Native.Module.callNativeModuleWith' a callback method whose result is -- decoded via 'Miso.JSON.FromJSON'. @NativeModules@ exists __only on the BTS__: -- -- @ -- 'Miso.Native.Module.callNativeModule' \"NativeLocalStorageModule\" \"setStorageItem\" -- [ 'Miso.JSON.String' \"key\", 'Miso.JSON.String' \"value\" ] -- @ -- -- 'update' runs on the BTS by default, so this just works there; from a -- main-thread handler, hop to the BTS first with 'Miso.Effect.runOnBG'. On the -- MTS the module is @undefined@ and the call logs a @consoleError@. -- -- * __Main-thread element ops (MTS-only)__ — the imperative helpers in -- "Miso.Native.MainThread" ('Miso.Native.MainThread.setStyleProperty' etc.) and -- the element PAPI they call exist __only on the MTS__; on the BTS they no-op. -- Drive them from a @*Main@ handler or via 'Miso.Effect.runOnMain'. -- -- = A minimal native component -- -- @ -- ----------------------------------------------------------------------------- -- {-# LANGUAGE StaticPointers #-} -- ----------------------------------------------------------------------------- -- import "Miso" -- import "Miso.Native" -- ----------------------------------------------------------------------------- -- view :: context -> props -> Model -> 'Miso.Types.View' context Action -- view _ _ m = -- 'vfrag_' -- [ 'view_' [ 'Miso.Native.Element.View.Event.onTap' Increment ] [ 'text_' [] [ \"+\" ] ] -- , 'text_' [] [ 'text' $ 'Miso.String.ms' ('show' m) ] -- , 'view_' [ 'Miso.Native.Element.View.Event.onTap' Decrement ] [ 'text_' [] [ \"-\" ] ] -- ] -- @ -- -- More information on how to use miso is available on GitHub -- -- <http://github.com/dmjio/miso> -- ---------------------------------------------------------------------------- module Miso.Native ( -- * Entrypoint native , nativeWithContext -- * 'Miso.Types.Component' mounting , mountStatic_ , mountStaticWithProps , mountStaticUseContext -- * Element , module Miso.Native.Element -- * FFI , module Miso.Native.FFI -- * Event , module Miso.Native.Event ) where ----------------------------------------------------------------------------- import Miso.Runtime (initComponent) import Miso.Types (Events, SomeStaticComponent(..), SomeComponent(..), Hydrate(..)) import Miso.Types (mountStatic_, mountStaticWithProps, mountStaticUseContext) import Miso.JSON (ToJSON, FromJSON) ----------------------------------------------------------------------------- import Miso.Native.Element import Miso.Native.FFI import Miso.Native.Event ----------------------------------------------------------------------------- import GHC.StaticPtr (StaticPtr, deRefStaticPtr, staticKey) ----------------------------------------------------------------------------- -- | The native drawing context is already selected per-thread by the runtime -- (@ts\/miso-native.ts@ picks @bts@ or @mts@ from @__BACKGROUND__@), so there -- is no renderer to register — we start the app directly. -- -- @ -- {-# LANGUAGE StaticPointers #-} -- -- import Miso -- import Miso.Native -- -- main :: IO () -- main = native nativeEvents (static (mountStatic_ app)) -- @ -- native :: Events -> StaticPtr (SomeStaticComponent () ()) -> IO () native :: Events -> StaticPtr (SomeStaticComponent () ()) -> IO () native Events events StaticPtr (SomeStaticComponent () ()) ptr = case StaticPtr (SomeStaticComponent () ()) -> SomeStaticComponent () () forall a. StaticPtr a -> a deRefStaticPtr StaticPtr (SomeStaticComponent () ()) ptr of SomeStaticComponent () -> SomeComponent () mk -> case () -> SomeComponent () mk () of SomeComponent Maybe Key key props props_ Component () props model action vcomp_ -> Events -> Hydrate -> Bool -> () -> Component () props model action -> Maybe Key -> props -> Maybe StaticKey -> IO () forall context props model action. (Eq context, Eq model, Eq props, ToJSON model, ToJSON props, ToJSON action, FromJSON action) => Events -> Hydrate -> Bool -> context -> Component context props model action -> Maybe Key -> props -> Maybe StaticKey -> IO () initComponent Events events Hydrate Draw Bool False () Component () props model action vcomp_ Maybe Key key props props_ (StaticKey -> Maybe StaticKey forall a. a -> Maybe a Just (StaticPtr (SomeStaticComponent () ()) -> StaticKey forall a. StaticPtr a -> StaticKey staticKey StaticPtr (SomeStaticComponent () ()) ptr)) ----------------------------------------------------------------------------- -- | Like 'native', but the user can specify a global 'context' object. -- -- @ -- {-# LANGUAGE StaticPointers #-} -- -- import "Miso" -- import "Miso.Native" -- -- main :: IO () -- main = 'nativeWithContext' 'nativeEvents' () (static ('mountStatic_' app)) -- @ -- nativeWithContext :: (ToJSON context, FromJSON context, Eq context) => Events -> context -> StaticPtr (SomeStaticComponent () context) -> IO () nativeWithContext :: forall context. (ToJSON context, FromJSON context, Eq context) => Events -> context -> StaticPtr (SomeStaticComponent () context) -> IO () nativeWithContext Events events context context StaticPtr (SomeStaticComponent () context) ptr = case StaticPtr (SomeStaticComponent () context) -> SomeStaticComponent () context forall a. StaticPtr a -> a deRefStaticPtr StaticPtr (SomeStaticComponent () context) ptr of SomeStaticComponent () -> SomeComponent context mk -> case () -> SomeComponent context mk () of SomeComponent Maybe Key key props props_ Component context props model action vcomp_ -> Events -> Hydrate -> Bool -> context -> Component context props model action -> Maybe Key -> props -> Maybe StaticKey -> IO () forall context props model action. (Eq context, Eq model, Eq props, ToJSON model, ToJSON props, ToJSON action, FromJSON action) => Events -> Hydrate -> Bool -> context -> Component context props model action -> Maybe Key -> props -> Maybe StaticKey -> IO () initComponent Events events Hydrate Draw Bool False context context Component context props model action vcomp_ Maybe Key key props props_ (StaticKey -> Maybe StaticKey forall a. a -> Maybe a Just (StaticPtr (SomeStaticComponent () context) -> StaticKey forall a. StaticPtr a -> StaticKey staticKey StaticPtr (SomeStaticComponent () context) ptr)) -----------------------------------------------------------------------------