Skip to content

Instantly share code, notes, and snippets.

@ruxo
Last active April 28, 2023 23:20
Show Gist options
  • Select an option

  • Save ruxo/6c21b773ce4cdb3cfc82deeafe632bd1 to your computer and use it in GitHub Desktop.

Select an option

Save ruxo/6c21b773ce4cdb3cfc82deeafe632bd1 to your computer and use it in GitHub Desktop.
(Broken) AsyncVal for UI async execution for FSharp.Data.Adaptive
[<AutoOpen>]
module Tirax.KMS.AdaptiveExtension
open System
open System.Runtime.CompilerServices
open FSharp.Data.Adaptive
[<Struct; IsReadOnly>]
type AsyncResult<'T> =
| Loading
| LoadError of error:exn
| Data of 'T
[<Struct; IsReadOnly>]
type CachedValue<'K, 'T when 'K :equality> = {
input :'K
output :AsyncResult<'T>
last_updated :DateTime
}
with
member my.isSame time_limit v = my.input = v && DateTime.Now - my.last_updated < time_limit
let private setCache struct (k,v) = { input=k; output=v; last_updated = DateTime.Now }
[<Sealed>]
type AsyncVal<'TI, 'TO when 'TI :equality>(async_func :'TI -> Async<'TO>, input :aval<'TI>) =
inherit AdaptiveObject()
let mutable current = { input=Unchecked.defaultof<'TI>; output = Loading; last_updated = DateTime.MinValue }
interface IAdaptiveValue<AsyncResult<'TO>> with
member my.GetValue t = my.GetValue t
member me.Accept(visitor) = visitor.Visit me
member my.ContentType = typeof<AsyncResult<'TO>>
member my.GetValueUntyped(token) = my.GetValue(token) |> box
member my.GetValue token = my.EvaluateIfNeeded token current.output my.compute
member private my.compute(token) =
let k = input.GetValue token
if not <| current.isSame (TimeSpan.FromSeconds 1) k then
current <- setCache struct (k, Loading)
my.scheduleUpdate k
current.output
member private my.scheduleUpdate input_value =
Async.StartWithContinuations
(async_func input_value,
(fun v -> my.publish(input_value, Data v)),
(fun e -> my.publish(input_value, LoadError e)),
(fun _ -> my.publish(input_value, current.output)))
member private my.publish(k,v) =
transact(fun () -> current <- setCache(k,v)
my.MarkOutdated())
[<Extension>]
type AValExtension =
[<Extension>]
static member inline mapAsync(my :aval<'T>, async_func) =
AsyncVal(async_func, my) :> aval<AsyncResult<'R>>
[<AutoOpen>]
module Fun.Blazor.DslObservable
open System
open System.Reactive.Subjects
open System.Runtime.CompilerServices
open System.Runtime.InteropServices
open FSharp.Control.Reactive
open RZ.FSharp.Extension
[<Struct; IsReadOnly>]
type UiAsyncResult<'T> =
| Loading
| LoadError of error:exn
| Data of 'T
let uiAsync f x = async {
try
let! data = f x
return (Data data)
with
| e -> return (LoadError e)
}
module Helpers =
let empty_attrs = Internal.emptyAttr()
let empty_node = Internal.emptyNode()
let render (x :NodeRenderFragment) = x
[<Struct; IsByRefLike>]
type ObservableModel<'T>(observable :IObservable<'T>, [<Optional>] initial_view :NodeRenderFragment voption) =
member _.Observable = observable
member _.InitialView = initial_view
open Operators
type IObservable<'T> with
member my.bindUiAsync(f) =
my.bind(fun x -> Observable.single(Loading).concat (Observable.ofAsync(uiAsync f x)))
member source.render(render :'T -> NodeRenderFragment, default_value :'T, k :obj voption) =
ComponentWithChildBuilder<ReactiveComponent<'T>>() {
"DefaultValue" => default_value
"Store" => source
"RenderFn" => render
match k with
| ValueSome k -> html.key k
| ValueNone -> Internal.emptyAttr()
}
[<Struct; IsReadOnly>]
type ObserviewBuilder([<Optional; DefaultParameterValue null>] k :obj) =
member inline _.Zero() = Seq.empty
member inline _.Yield(x :NodeRenderFragment) = Seq.singleton x
member inline _.Delay([<InlineIfLambda>] fn :unit -> NodeRenderFragment seq) = fn()
member inline _.Combine(x :NodeRenderFragment seq, y :NodeRenderFragment seq) = y.append(x)
member _.Bind(source :IObservable<'T>, f :'T -> NodeRenderFragment seq) =
source.render(f >> html.mergeNodes, Unchecked.defaultof<'T>, ValueOption.ofObj k) |> Seq.singleton
member _.Bind(x :ObservableModel<'T>, f :'T -> NodeRenderFragment seq) =
let source = x.Observable |> Observable.map ValueSome
let init_view = x.InitialView
let render opt = match opt with
| ValueSome v -> v |> f |> html.mergeNodes
| ValueNone -> init_view.defaultValue(Helpers.empty_node)
source.render(render, ValueNone, ValueOption.ofObj k) |> Seq.singleton
member inline _.Run(x :NodeRenderFragment seq) =
html.mergeNodes(x)
[<Extension>]
type Extensions =
[<Extension>]
static member WithSetter(this :ISubject<'T>) =
this |> Observable.map (fun x -> struct (x, this.OnNext))
type obserview = ObserviewBuilder
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment