Skip to content

Commit f424eea

Browse files
committed
AMap deltaA implementation
1 parent d69fe5f commit f424eea

3 files changed

Lines changed: 128 additions & 2 deletions

File tree

src/FSharp.Data.Adaptive/AdaptiveHashMap/AdaptiveHashMap.fs

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -755,7 +755,85 @@ module AdaptiveHashMapImplementation =
755755
changes <- HashMap.add i (Set v) changes
756756

757757
HashMapDelta.ofHashMap changes
758-
758+
759+
/// Reader for deltaA operations.
760+
[<Sealed>]
761+
type DeltaAReader<'k, 'a, 'b>(input : amap<'k, 'a>, mapping : HashMap<'k,'a> -> HashMap<'k, aval<'b>>) =
762+
inherit AbstractReader<HashMapDelta<'k, 'b>>(HashMapDelta.empty)
763+
764+
let reader = input.GetReader()
765+
do reader.Tag <- "input"
766+
let cacheLock = obj()
767+
let mutable cache: HashMap<'k, aval<'b>> = HashMap.Empty
768+
let mutable targets = MultiSetMap.empty<aval<'b>, 'k>
769+
let mutable dirty = HashMap.empty<'k, aval<'b>>
770+
771+
let consumeDirty() =
772+
lock cacheLock (fun () ->
773+
let d = dirty
774+
dirty <- HashMap.empty
775+
d
776+
)
777+
778+
override x.InputChangedObject(t, o) =
779+
#if FABLE_COMPILER
780+
if isNull o.Tag then
781+
let o = unbox<aval<'b>> o
782+
for i in MultiSetMap.find o targets do
783+
dirty <- HashMap.add i o dirty
784+
#else
785+
match o with
786+
| :? aval<'b> as o ->
787+
lock cacheLock (fun () ->
788+
for i in MultiSetMap.find o targets do
789+
dirty <- HashMap.add i o dirty
790+
)
791+
| _ ->
792+
()
793+
#endif
794+
795+
override x.Compute t =
796+
let mutable dirty = consumeDirty()
797+
let old = reader.State
798+
let ops = reader.GetChanges t |> HashMapDelta.toHashMap
799+
800+
let setOps, removeOps =
801+
((HashMap.empty, HashMap.empty), ops)
802+
||> HashMap.fold(fun (sets, rems) i op ->
803+
dirty <- HashMap.remove i dirty
804+
cache <-
805+
match HashMap.tryRemove i cache with
806+
| Some (o, remaingCache) ->
807+
let rem, rest = MultiSetMap.remove o i targets
808+
targets <- rest
809+
if rem then o.Outputs.Remove x |> ignore
810+
remaingCache
811+
| None -> cache
812+
match op with
813+
| Set v ->
814+
HashMap.add i v sets, rems
815+
| Remove ->
816+
sets, HashMap.add i Remove rems
817+
)
818+
819+
let mutable changes =
820+
setOps
821+
|> mapping
822+
|> HashMap.map(fun i k ->
823+
cache <- HashMap.add i k cache
824+
let v = k.GetValue t
825+
targets <- MultiSetMap.add k i targets
826+
Set v
827+
)
828+
829+
830+
for i, d in dirty do
831+
let v = d.GetValue t
832+
changes <- HashMap.add i (Set v) changes
833+
834+
HashMap.union removeOps changes
835+
|> HashMapDelta
836+
759837
/// Reader for chooseA operations.
760838
[<Sealed>]
761839
type ChooseAReader<'k, 'a, 'b>(input : amap<'k, 'a>, mapping : 'k -> 'a -> aval<Option<'b>>) =
@@ -1333,6 +1411,19 @@ module AMap =
13331411
else
13341412
create (fun () -> MapAReader(map, mapping))
13351413

1414+
/// Adaptively applies the given mapping to all changes.
1415+
let deltaA (mapping: HashMap<'K,'T1> -> HashMap<'K,aval<'T2>>) (map: amap<'K, 'T1>) =
1416+
if map.IsConstant then
1417+
let map = force map |> mapping
1418+
if map |> HashMap.forall (fun _ v -> v.IsConstant) then
1419+
constant (fun () -> map |> HashMap.map (fun _ v -> AVal.force v))
1420+
else
1421+
// TODO better impl possible
1422+
create (fun () -> MapAReader(ofHashMap map, fun _ v -> v))
1423+
else
1424+
create (fun () -> DeltaAReader(map, mapping))
1425+
1426+
13361427
/// Adaptively chooses all elements returned by mapping.
13371428
let chooseA (mapping: 'K ->'T1 -> aval<Option<'T2>>) (map: amap<'K, 'T1>) =
13381429
if map.IsConstant then

src/FSharp.Data.Adaptive/AdaptiveHashMap/AdaptiveHashMap.fsi

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,12 @@ module AMap =
102102
/// Adaptively intersects the two maps.
103103
val intersectV : amap<'Key, 'Value1> -> amap<'Key, 'Value2> -> amap<'Key, struct('Value1 * 'Value2)>
104104

105-
106105
/// Adaptively applies the given mapping function to all elements and returns a new amap containing the results.
107106
val mapA : mapping: ('K -> 'V -> aval<'T>) -> map: amap<'K, 'V> -> amap<'K, 'T>
108107

108+
/// Adaptively applies the given mapping to all changes.
109+
val deltaA : mapping: (HashMap<'K,'T1> -> HashMap<'K,aval<'T2>>) -> map: amap<'K, 'T1> -> amap<'K, 'T2>
110+
109111
/// Adaptively chooses all elements returned by mapping.
110112
val chooseA : mapping: ('K -> 'V -> aval<option<'T>>) -> map: amap<'K, 'V> -> amap<'K, 'T>
111113

src/Test/FSharp.Data.Adaptive.Tests/AMap.fs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -638,4 +638,37 @@ let ``[AMap] mapA``() =
638638
flag.Value <- true
639639
)
640640

641+
res |> AMap.force |> should equal (HashMap.ofList ["A", 2; "B", 4; "C", 6])
642+
643+
644+
645+
[<Test>]
646+
let ``[AMap] deltaA``() =
647+
let map = cmap ["A", 1; "B", 2; "C", 3]
648+
let flag = cval true
649+
650+
let res =
651+
map |> AMap.deltaA (fun d ->
652+
d
653+
|> HashMap.map(fun _ v -> flag |> AVal.map (function true -> v | false -> -1))
654+
)
655+
656+
res |> AMap.force |> should equal (HashMap.ofList ["A", 1; "B", 2; "C", 3])
657+
658+
transact (fun () ->
659+
flag.Value <- false
660+
)
661+
662+
res |> AMap.force |> should equal (HashMap.ofList ["A", -1; "B", -1; "C", -1])
663+
664+
transact (fun () ->
665+
map.Value <- map.Value |> HashMap.map (fun _ v -> v * 2)
666+
)
667+
668+
res |> AMap.force |> should equal (HashMap.ofList ["A", -1; "B", -1; "C", -1])
669+
670+
transact (fun () ->
671+
flag.Value <- true
672+
)
673+
641674
res |> AMap.force |> should equal (HashMap.ofList ["A", 2; "B", 4; "C", 6])

0 commit comments

Comments
 (0)