{"action":"created","issue":{"url":"https://api.github.com/repos/intellifactory/websharper.ui.next/issues/35","labels_url":"https://api.github.com/repos/intellifactory/websharper.ui.next/issues/35/labels{/name}","comments_url":"https://api.github.com/repos/intellifactory/websharper.ui.next/issues/35/comments","events_url":"https://api.github.com/repos/intellifactory/websharper.ui.next/issues/35/events","html_url":"https://github.com/intellifactory/websharper.ui.next/issues/35","id":105571116,"number":35,"title":"Snap.SnapshotOn when both snaps change at the same time","user":{"login":"Bananas-Are-Yellow","id":5648989,"avatar_url":"https://avatars.githubusercontent.com/u/5648989?v=3","gravatar_id":"","url":"https://api.github.com/users/Bananas-Are-Yellow","html_url":"https://github.com/Bananas-Are-Yellow","followers_url":"https://api.github.com/users/Bananas-Are-Yellow/followers","following_url":"https://api.github.com/users/Bananas-Are-Yellow/following{/other_user}","gists_url":"https://api.github.com/users/Bananas-Are-Yellow/gists{/gist_id}","starred_url":"https://api.github.com/users/Bananas-Are-Yellow/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Bananas-Are-Yellow/subscriptions","organizations_url":"https://api.github.com/users/Bananas-Are-Yellow/orgs","repos_url":"https://api.github.com/users/Bananas-Are-Yellow/repos","events_url":"https://api.github.com/users/Bananas-Are-Yellow/events{/privacy}","received_events_url":"https://api.github.com/users/Bananas-Are-Yellow/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":4,"created_at":"2015-09-09T10:44:10Z","updated_at":"2015-09-11T12:36:52Z","closed_at":null,"body":"I'm trying to control update propagation of views so that update only occurs when the value has changed.\r\n\r\nHere is an example. I am dragging the mouse and I want to convert a view of XY positions into a view of distances from the mouse-down position, where these distances are snapped to the nearest value of 10.\r\n\r\n```fsharp\r\nlet snap (dist: float) x = dist * Math.Round (x / dist)\r\nlet inline private sq x = x * x\r\nlet dist (stX, stY) (enX, enY) =\r\n    sq (stX - enX) + sq (stY - enY) |> float |> sqrt |> snap 10\r\n\r\n// set the value only if it has changed\r\nlet inline assign (var: Var<_>) value = if value <> var.Value then var.Value <- value\r\n\r\nlet distView down pos = // result is View<float>\r\n    let state = Var.Create 0.\r\n    pos // input is View<int * int>\r\n    |> View.Bind (fun pos ->\r\n        assign state (dist down pos)\r\n        state.View)\r\n    |> View.SnapshotOn state.Value state.View\r\n```\r\n\r\nAs you drag away from the mouse-down position, many XY positions will produce the first snapped distance, 0, and then many more XY positions will produce the next distance, 10, and then 20, and so on.\r\n\r\nThe usual behavior of computed views (`View.Map`, `View.Bind`, etc.) is that whenever input views are updated (their `Snap` becomes obsolete), this marks the computed view snap as obsolete, and so on, all the way up the dependency tree. A new snap value will be recalculated if and when it is next demanded.\r\n\r\nThis is even true for `View.UpdateWhile`. When the first view is false, the computed view will nevertheless update each time the second view is updated, and return the last captured value of the second view over and over.\r\n\r\nThis is fine in that the values are correct, but if the calculation done inside a `View.Map` function is expensive, we don't want to repeat the calculation when the value hasn't changed. In my case, the calculation done with the distance (not shown here) is expensive, but this needs to be efficient since it is going on while the user is dragging the mouse.\r\n\r\nIn the code above, at first glance it looks like `View.Bind` returns `state.View`, which doesn't change all the time, but in fact `View.Bind` creates a new computed view, which just happens to have the value of `state.View` in this example. So even if `state.View` hasn't changed, `View.Bind` will create a new snap for each new XY position, copying the value for this snap out of `state.View`.\r\n\r\nSo my idea was to use `View.SnapshotOn` at the end, so that the resulting view is only updated when `state.View` has actually changed value. But sadly it doesn't work, and I only get the default value.\r\n\r\nI believe the problem might be in `Snap.SnapshotOn`. Here's the code:\r\n\r\n```fsharp\r\nlet SnapshotOn sn1 sn2 =\r\n    match sn1.State, sn2.State with\r\n    | _, Forever y -> CreateForever y\r\n    | _ ->\r\n        let res = Create ()\r\n        let v = ref None\r\n        let triggered = ref false\r\n\r\n        let obs () =\r\n            v := None\r\n            MarkObsolete res\r\n\r\n        let cont () =\r\n            if !triggered then\r\n                match !v with\r\n                | Some y when IsForever sn2 -> MarkForever res y\r\n                | Some y -> MarkReady res y\r\n                | _ -> ()\r\n        When sn1 (fun x -> triggered := true; cont ()) obs\r\n        When sn2 (fun y -> v := Some y; cont ()) ignore\r\n        res\r\n```\r\n\r\nI'm not sure what this `triggered` test is all about, but it might be the problem. As I see it, we want the value from `sn2` to be applied to `res`, and we want `res` to be made obsolete when `sn1` becomes obsolete.\r\n\r\nPerhaps I don't understand the code. Am I misusing `View.SnapshotOn` or is there a bug?"},"comment":{"url":"https://api.github.com/repos/intellifactory/websharper.ui.next/issues/comments/139535027","html_url":"https://github.com/intellifactory/websharper.ui.next/issues/35#issuecomment-139535027","issue_url":"https://api.github.com/repos/intellifactory/websharper.ui.next/issues/35","id":139535027,"user":{"login":"Tarmil","id":921395,"avatar_url":"https://avatars.githubusercontent.com/u/921395?v=3","gravatar_id":"","url":"https://api.github.com/users/Tarmil","html_url":"https://github.com/Tarmil","followers_url":"https://api.github.com/users/Tarmil/followers","following_url":"https://api.github.com/users/Tarmil/following{/other_user}","gists_url":"https://api.github.com/users/Tarmil/gists{/gist_id}","starred_url":"https://api.github.com/users/Tarmil/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Tarmil/subscriptions","organizations_url":"https://api.github.com/users/Tarmil/orgs","repos_url":"https://api.github.com/users/Tarmil/repos","events_url":"https://api.github.com/users/Tarmil/events{/privacy}","received_events_url":"https://api.github.com/users/Tarmil/received_events","type":"User","site_admin":false},"created_at":"2015-09-11T12:36:52Z","updated_at":"2015-09-11T12:36:52Z","body":"For the use case of avoiding the recomputation of a mapped value, I just PR'd a `MapCached` function #39. Can you check if this does what you need?"}}