diff --git a/CHANGELOG.md b/CHANGELOG.md index 059f15f..3edb17d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ All notable changes to this project will be documented in this file. This change * External windows not loading * All app-db and subscription path expansions are now independent of each other [#134](https://github.com/Day8/re-frame-trace/issues/134). +* Layer 2/3 calculations are more accurate now. We now use the last seen layer level when a subscription runs, to inform it's layer level if it was created or destroyed. diff --git a/docs/HyperlinkedInformation/UnchangedLayer2.md b/docs/HyperlinkedInformation/UnchangedLayer2.md index 52948fb..97485e1 100644 --- a/docs/HyperlinkedInformation/UnchangedLayer2.md +++ b/docs/HyperlinkedInformation/UnchangedLayer2.md @@ -1,43 +1,59 @@ - -This document briefly explains why `re-frame-trace` gives you an option to -ignore unchanged layer 2 subscriptions. +This document briefly explains why `re-frame-trace` gives you an option to +ignore unchanged layer 2 subscriptions. ### Background -The `re-frame` docs +The `re-frame` docs [make a distinction](https://github.com/Day8/re-frame/blob/master/docs/SubscriptionInfographic.md) between `layer 2` and `layer 3` subscriptions: - `layer 2` subscriptions extract data directly from `app-db` and should be trivial in nature. There should be no computation in them beyond what is necessary to extract a value from `app-db` - - `layer 3` subscriptions take values from `layer 2` nodes as inputs, and - compute a materialised view of those values. Just to repeat: they never directly + - `layer 3` subscriptions take values from `layer 2` nodes as inputs, and + compute a materialised view of those values. Just to repeat: they never directly extract values from `app-db`. They create new values where necessary, and because of it they to do more serious CPU work. So we never want to run a - `layer 3` subscriptions unless it is necessary. - -This structure delivers efficiency. You see, **all** (currently instantiated) `layer 2` subscriptions + `layer 3` subscriptions unless it is necessary. + +This structure delivers efficiency. You see, **all** (currently instantiated) `layer 2` subscriptions will run **every** time `app-db` changes in any way. All of them. Every time. -And `app-db` changes on almost every event, so we want them to be computationally -trivial. +And `app-db` changes on almost every event, so we want them to be computationally +trivial. If the value of a `layer 2` subscription tests `=` to its previous value, then the further propagation of values through the signal graph will be pruned. The more computationally intensive `layer 3` subscriptions, and ultimately the views, will only recompute if and when there has been a change in their data inputs. -We don't want your app recomputing views only to find that nothing has changed. Inefficient. +We don't want your app recomputing views only to find that nothing has changed. Inefficient. ### Back To Tracing Because `layer 2` subs run on every single modification of `app-db`, and because -very often nothing has changed, their trace can be a bit noisy. Yes, it happened, +very often nothing has changed, their trace can be a bit noisy. Yes, it happened, but it just isn't that interesting. -So `re-frame-trace` gives you the option of filtering out trace for -the `layer 2` subscriptions where the value "this time" is the same as the +So `re-frame-trace` gives you the option of filtering out trace for +the `layer 2` subscriptions where the value "this time" is the same as the value "last time". -On the other hand, if a `layer 2` subscription runs and its value is -different to last time, that's potentially fascinating and you'll want to -be told all about it. :-) +On the other hand, if a `layer 2` subscription runs and its value is +different to last time, that's potentially fascinating and you'll want to +be told all about it. :-) + +### Why do I sometimes see "Layer ?" when viewing a subscription? + +To determine whether a subscription is a layer 2 or layer 3, re-frame-trace +looks at the input signals to a subscription. If one of the input signals is +app-db then the subscription is a layer 2 sub, otherwise it is a layer 3. If +a subscription hasn't run yet, then we can't know if it is a layer 2 or 3. + +In almost all cases, a subscription will be created (by `(subscribe [:my-sub])`) +and run (by dereferencing the subscription) within the same epoch, providing +the layer level. If you see "Layer ?" this means that a subscription was created +but not used. This may indicate a bug in your application, although there are +cases where this is ok. + +In most cases, after a few more epochs, that subscription will have run, and we +know it's layer level, and can use it for any subscriptions shown on any future +(and past) epochs. diff --git a/src/day8/re_frame/trace/events.cljs b/src/day8/re_frame/trace/events.cljs index 18eae6b..f650d2f 100644 --- a/src/day8/re_frame/trace/events.cljs +++ b/src/day8/re_frame/trace/events.cljs @@ -510,10 +510,26 @@ (contains? events-to-ignore (first event)))) new-matches) all-matches (reduce conj previous-matches new-matches) retained-matches (into [] (take-last number-of-epochs-to-retain all-matches)) + app-db-id (get-in db [:app-db :reagent-id]) + subscription-info (->> new-traces + (filter metam/subscription-re-run?) + (reduce (fn [state trace] + ;; TODO: can we take any shortcuts by assuming that a sub with + ;; multiple input signals is a layer 3? I don't *think* so because + ;; one of those input signals could be a naughty subscription to app-db + ;; directly. + ;; If any of the input signals are app-db, it is a layer 2 sub, else 3 + (assoc-in state + [(:operation trace) :layer] + (if (some #(= app-db-id %) (get-in trace [:tags :input-signals])) + 2 + 3))) + (get-in db [:epochs :subscription-info] {}))) first-id-to-retain (:id (ffirst retained-matches)) retained-traces (into [] (comp (drop-while #(< (:id %) first-id-to-retain)) - (remove (fn [trace] (or (when drop-reagent (metam/low-level-reagent-trace? trace)) - (when drop-re-frame (metam/low-level-re-frame-trace? trace)))))) all-traces)] + (remove (fn [trace] + (or (when drop-reagent (metam/low-level-reagent-trace? trace)) + (when drop-re-frame (metam/low-level-re-frame-trace? trace)))))) all-traces)] (-> db (assoc-in [:traces :all-traces] retained-traces) (update :epochs (fn [epochs] @@ -521,7 +537,8 @@ :matches retained-matches :matches-by-id (into {} (map (juxt first-match-id identity)) retained-matches) :match-ids (mapv first-match-id retained-matches) - :parse-state parse-state))))) + :parse-state parse-state + :subscription-info subscription-info))))) ;; Else db))) diff --git a/src/day8/re_frame/trace/subs.cljs b/src/day8/re_frame/trace/subs.cljs index 7ae5375..4746e60 100644 --- a/src/day8/re_frame/trace/subs.cljs +++ b/src/day8/re_frame/trace/subs.cljs @@ -351,6 +351,12 @@ (fn [traces] (filter metam/subscription? traces))) +(rf/reg-sub + :subs/subscription-info + :<- [:epochs/epoch-root] + (fn [epoch] + (:subscription-info epoch))) + (defn sub-sort-val [sub] (case (:type sub) @@ -375,7 +381,8 @@ :subs/all-subs :<- [:subs/all-sub-traces] :<- [:app-db/reagent-id] - (fn [[traces app-db-id]] + :<- [:subs/subscription-info] + (fn [[traces app-db-id sub-info]] (let [raw (map (fn [trace] (let [pod-type (sub-op-type->type trace) path-data (get-in trace [:tags :query-v]) ;; TODO: detect layer 2/3 for sub/create and sub/destroy @@ -385,7 +392,7 @@ 3)] {:id (str pod-type (get-in trace [:tags :reaction])) :type pod-type - :layer layer + :layer (get-in sub-info [(:operation trace) :layer]) :path-data path-data :path (pr-str path-data) :value (get-in trace [:tags :value]) diff --git a/src/day8/re_frame/trace/view/subs.cljs b/src/day8/re_frame/trace/view/subs.cljs index 190fb2a..0025254 100644 --- a/src/day8/re_frame/trace/view/subs.cljs +++ b/src/day8/re_frame/trace/view/subs.cljs @@ -133,7 +133,7 @@ :model path :disabled? true]]] [rc/gap-f :size common/gs-12s] - [rc/label :label (str "Layer " layer)] + [rc/label :label (str "Layer " (if (some? layer) layer "?"))] ;; TODO: capture previous sub run value and allow diffing it. #_[rc/gap-f :size common/gs-12s]