Skip to content

Commit 1124556

Browse files
committed
BatchRecalcDirty
1 parent f424eea commit 1124556

3 files changed

Lines changed: 121 additions & 39 deletions

File tree

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

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -756,9 +756,9 @@ module AdaptiveHashMapImplementation =
756756

757757
HashMapDelta.ofHashMap changes
758758

759-
/// Reader for deltaA operations.
759+
/// Reader for batchRecalc operations.
760760
[<Sealed>]
761-
type DeltaAReader<'k, 'a, 'b>(input : amap<'k, 'a>, mapping : HashMap<'k,'a> -> HashMap<'k, aval<'b>>) =
761+
type BatchRecalculateDirty<'k, 'a, 'b>(input : amap<'k, 'a>, mapping : HashMap<'k,'a> -> HashMap<'k, aval<'b>>) =
762762
inherit AbstractReader<HashMapDelta<'k, 'b>>(HashMapDelta.empty)
763763

764764
let reader = input.GetReader()
@@ -787,6 +787,7 @@ module AdaptiveHashMapImplementation =
787787
lock cacheLock (fun () ->
788788
for i in MultiSetMap.find o targets do
789789
dirty <- HashMap.add i o dirty
790+
790791
)
791792
| _ ->
792793
()
@@ -816,19 +817,22 @@ module AdaptiveHashMapImplementation =
816817
sets, HashMap.add i Remove rems
817818
)
818819

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-
)
828820

821+
let mutable changes = HashMap.empty
822+
let setOps =
823+
(setOps, dirty)
824+
||> HashMap.fold(fun s k v ->
825+
match HashMap.tryFind k old with
826+
| Some v ->
827+
HashMap.add k v s
828+
| None ->
829+
s
830+
)
829831

830-
for i, d in dirty do
831-
let v = d.GetValue t
832+
for i, k in mapping setOps do
833+
cache <- HashMap.add i k cache
834+
let v = k.GetValue t
835+
targets <- MultiSetMap.add k i targets
832836
changes <- HashMap.add i (Set v) changes
833837

834838
HashMap.union removeOps changes
@@ -1411,17 +1415,17 @@ module AMap =
14111415
else
14121416
create (fun () -> MapAReader(map, mapping))
14131417

1414-
/// Adaptively applies the given mapping to all changes.
1415-
let deltaA (mapping: HashMap<'K,'T1> -> HashMap<'K,aval<'T2>>) (map: amap<'K, 'T1>) =
1418+
/// Adaptively applies the given mapping to all changes and reapplies mapping on dirty outputs
1419+
let batchRecalcDirty (mapping: HashMap<'K,'T1> -> HashMap<'K,aval<'T2>>) (map: amap<'K, 'T1>) =
14161420
if map.IsConstant then
14171421
let map = force map |> mapping
14181422
if map |> HashMap.forall (fun _ v -> v.IsConstant) then
14191423
constant (fun () -> map |> HashMap.map (fun _ v -> AVal.force v))
14201424
else
14211425
// TODO better impl possible
1422-
create (fun () -> MapAReader(ofHashMap map, fun _ v -> v))
1426+
create (fun () -> BatchRecalculateDirty(ofHashMap map, id))
14231427
else
1424-
create (fun () -> DeltaAReader(map, mapping))
1428+
create (fun () -> BatchRecalculateDirty(map, mapping))
14251429

14261430

14271431
/// Adaptively chooses all elements returned by mapping.

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ module AMap =
106106
val mapA : mapping: ('K -> 'V -> aval<'T>) -> map: amap<'K, 'V> -> amap<'K, 'T>
107107

108108
/// 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>
109+
val batchRecalcDirty : mapping: (HashMap<'K,'T1> -> HashMap<'K,aval<'T2>>) -> map: amap<'K, 'T1> -> amap<'K, 'T2>
110110

111111
/// Adaptively chooses all elements returned by mapping.
112112
val chooseA : mapping: ('K -> 'V -> aval<option<'T>>) -> map: amap<'K, 'V> -> amap<'K, 'T>

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

Lines changed: 99 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ open FsUnit
88
open FsCheck.NUnit
99
open FSharp.Data
1010
open Generators
11+
open System.IO
12+
open System
1113

1214
[<Property(MaxTest = 500, Arbitrary = [| typeof<Generators.AdaptiveGenerators> |]); Timeout(60000)>]
1315
let ``[AMap] reference impl``() ({ mreal = real; mref = ref; mexpression = str; mchanges = changes } : VMap<int, int>) =
@@ -641,34 +643,110 @@ let ``[AMap] mapA``() =
641643
res |> AMap.force |> should equal (HashMap.ofList ["A", 2; "B", 4; "C", 6])
642644

643645

644-
645-
[<Test>]
646-
let ``[AMap] deltaA``() =
647-
let map = cmap ["A", 1; "B", 2; "C", 3]
648-
let flag = cval true
649646

647+
/// <summary>
648+
/// Calls a mapping function which creates additional dependencies to be tracked.
649+
/// </summary>
650+
let mapWithAdditionalDependenies (mapping: 'a -> 'b * #seq<#IAdaptiveValue>) (value: aval<'a>) : aval<'b> =
651+
let mutable lastDeps = HashSet.empty
652+
653+
{ new AVal.AbstractVal<'b>() with
654+
member x.Compute(token: AdaptiveToken) =
655+
let input = value.GetValue token
656+
657+
// re-evaluate the mapping based on the (possibly new input)
658+
let result, deps = mapping input
659+
660+
// compute the change in the additional dependencies and adjust the graph accordingly
661+
let newDeps = HashSet.ofSeq deps
662+
663+
for op in HashSet.computeDelta lastDeps newDeps do
664+
match op with
665+
| Add(_, d) ->
666+
// the new dependency needs to be evaluated with our token, s.t. we depend on it in the future
667+
d.GetValueUntyped token |> ignore
668+
| Rem(_, d) ->
669+
// we no longer need to depend on the old dependency so we can remove ourselves from its outputs
670+
lock d.Outputs (fun () -> d.Outputs.Remove x) |> ignore
671+
672+
lastDeps <- newDeps
673+
674+
result }
675+
:> aval<_>
676+
677+
[<Test>]
678+
let ``[AMap] batchRecalcDirty``() =
679+
680+
let file1 = "File1.fs"
681+
let file1Cval = cval 1
682+
let file1DepCval = cval 1
683+
let file2 = "File2.fs"
684+
let file2Cval = cval 2
685+
let file2DepCval = cval 1
686+
let file3 = "File3.fs"
687+
let file3Cval = cval 3
688+
let file3DepCval = cval 1
689+
690+
let m = Map [file1, file1DepCval; file2, file2DepCval; file3, file3DepCval]
691+
692+
let projs =
693+
[
694+
file1, file1Cval
695+
file2, file2Cval
696+
file3, file3Cval
697+
]
698+
|> AMap.ofList
699+
|> AMap.mapA(fun _ v -> v)
700+
701+
let mutable lastBatch = Unchecked.defaultof<_>
650702
let res =
651-
map |> AMap.deltaA (fun d ->
652-
d
653-
|> HashMap.map(fun _ v -> flag |> AVal.map (function true -> v | false -> -1))
703+
projs
704+
|> AMap.batchRecalcDirty(fun d ->
705+
lastBatch <- d
706+
HashMap.ofList [
707+
for k,v in d do
708+
k, (AVal.constant <| Guid.NewGuid()) |> mapWithAdditionalDependenies(fun a -> a, [m.[k]])
709+
]
654710
)
711+
let firstResult = res |> AMap.force
712+
lastBatch |> should haveCount 3
655713

656-
res |> AMap.force |> should equal (HashMap.ofList ["A", 1; "B", 2; "C", 3])
714+
transact(fun () -> file1Cval.Value <- file1Cval.Value + 1)
657715

658-
transact (fun () ->
659-
flag.Value <- false
660-
)
716+
let secondResult = res |> AMap.force
717+
lastBatch |> should haveCount 1
718+
719+
firstResult.[file1] |> should not' (equal secondResult.[file1])
720+
firstResult.[file2] |> should equal secondResult.[file2]
721+
firstResult.[file3] |> should equal secondResult.[file3]
661722

662-
res |> AMap.force |> should equal (HashMap.ofList ["A", -1; "B", -1; "C", -1])
663723

664-
transact (fun () ->
665-
map.Value <- map.Value |> HashMap.map (fun _ v -> v * 2)
666-
)
724+
transact(fun () ->
725+
file1Cval.Value <- file1Cval.Value + 1
726+
file3Cval.Value <- file3Cval.Value + 1)
727+
728+
let thirdResult = res |> AMap.force
729+
lastBatch |> should haveCount 2
667730

668-
res |> AMap.force |> should equal (HashMap.ofList ["A", -1; "B", -1; "C", -1])
731+
secondResult.[file1] |> should not' (equal thirdResult.[file1])
732+
secondResult.[file2] |> should equal thirdResult.[file2]
733+
secondResult.[file3] |> should not' (equal thirdResult.[file3])
669734

670-
transact (fun () ->
671-
flag.Value <- true
672-
)
673735

674-
res |> AMap.force |> should equal (HashMap.ofList ["A", 2; "B", 4; "C", 6])
736+
transact(fun () -> file1DepCval.Value <- file1DepCval.Value + 1)
737+
738+
let fourthResult = res |> AMap.force
739+
lastBatch |> should haveCount 1
740+
741+
thirdResult.[file1] |> should not' (equal fourthResult.[file1])
742+
743+
transact(fun () ->
744+
file1DepCval.Value <- file1DepCval.Value + 1
745+
file1Cval.Value <- file1Cval.Value)
746+
747+
let fifthResult = res |> AMap.force
748+
lastBatch |> should haveCount 1
749+
750+
fourthResult.[file1] |> should not' (equal fifthResult.[file1])
751+
752+
()

0 commit comments

Comments
 (0)