There are several problems/bugs with controlling updating/refreshing/initialization of specific panes/tabs in mentioned constructs:
-
Too sensitive refreshing:
'Visibility' of Dynamics in TabView with respect to SynchronousUpdating
-
Too insensitive refreshing...:
-
Flawed caching design:
Why is PaneSelector caching nested Dynamics and how to switch it off?
Let's try to create an enhanced version of TabView
which can meet following goals:
-
lazy: never refresh unless pane/tab is active
-
caching: don't re-evaluate if tracked parameters didn't change
-
caching: overcome FE caching which is based on input form of evaluated content.
-
refreshing: sensitive to Queued/Asynchronous changes of the symbol which controls visible pane.
-
refreshing: should handle long calculations gracefully
(* under construction... *)
(* we can't do real FE caching because even if we cache evalaution but FE needs to toggle view
the initialization will be run.
The implication is that it is better to design your view generating functions without initialization
unless it is really needed
*)
(*TODO: here's is an idea for front-end-caching, assuming fixed cache length, pregenerate PaneSelector
and *)
ClearAll[HDViewWrapper];
HDViewWrapper // Attributes={HoldAll};
HDViewWrapper // Options = {
"KernelCacheLength" -> 1
(*It is called kernel to stress that this won't cache FE view state, it only prevents unnecessary evaluation of view,
if otoh that view contains Initalization, it will be run if $view had to change in a meanwhile.
*)
, "EventMonitor" -> None
};
HDViewWrapper[
state_Dynamic, viewPos_Integer, spec_Rule, HDOptions : OptionsPattern[]
]:=HDViewWrapper[
state, viewPos, {viewPos, spec}, HDOptions
];
HDViewWrapper[
Dynamic[state_]
, viewPos_
, {viewId_, viewLabel_ -> Dynamic[view_, TrackedSymbols :> {tracked__}]}
, HDOptions : OptionsPattern[]
]:= With[
{ log = OptionValue["EventMonitor"]
, cacheLength = OptionValue["KernelCacheLength"]
}
, Module[{theView}
, theView = DynamicModule[
{$view = "", $cachedView,updateView, $updating
}
, DynamicWrapper[ (* listener *)
PaneSelector[
{ True -> ProgressIndicator[ Appearance->"Necklace" ]
, False -> Dynamic[$view]
}
, Dynamic[$updating+0] (* XD *)
, Alignment->{Center,Center}
]
, If[
state === viewId
, log[viewId, " listener: I will update"]; updateView[]
, log[viewId, " listener: I will not update: ", {state, viewId}]
]
, TrackedSymbols :> {tracked, state}
, SynchronousUpdating -> False
]
, UnsavedVariables:>{$view,$cachedView, updateView, $updating}
, Initialization:>(
$updating = True
; $cachedView[n_]:=(
log[viewId, " recalculation, new cache"]
; DownValues[$cachedView] = Reverse @ Take[Reverse @ DownValues[$cachedView], UpTo[cacheLength]]
(*we will cache only the last one but we need to keep a downvalue responsible for caching*)
(*notice that we get here only when conditions changed and cached value wasn't returned. *)
; $cachedView[n] = view
)
; updateView[]:=(
$updating=True
; log[viewId, " updating, checking cache"]
; $view = $cachedView[tracked]
; $updating=False
)
)
]
; {viewId, viewLabel -> theView}
]
]
HDViewWrapper[ Dynamic[state_], viewPos_, viewSpec_, OptionsPattern[] ]:=viewSpec;
HDViewWrapper[args___]:=Failure["InvalidArgument", "Message" ->InputForm@Hold[args]];
ClearAll[HDView];
HDView // Options = {
ImageSize -> Automatic
, "LayoutFunction" -> TabView
, "EventMonitor" -> None
};
HDView[ views:{_Rule..}|{{_, _Rule}..}, Dynamic[state_Symbol], patt: OptionsPattern[{HDView, TabView}] ]:= With[
{ wrapperOptions = FilterRules[{patt}, Options @ HDViewWrapper]
, tabViewOptions = Sequence @@ FilterRules[Join[{patt}, Options[HDView]], Options[OptionValue["LayoutFunction"]]]
, layoutFunction = OptionValue["LayoutFunction"]
}
, layoutFunction[
Table[ With[{pos = pos, viewSpec = views[[pos]]}
, HDViewWrapper[ Dynamic[state], pos, viewSpec, wrapperOptions ]
]
, {pos, Length[views]}
]
, Dynamic[state + 0, Function[new, state = new]]
, tabViewOptions
]
]
HDView[args___]:=Failure["InvalidArgument", "Message" ->InputForm@Hold[args]];
(*this functions comes from a third party package, the solution should not assume anything about it and should not expect anything *)
data=EntityValue[CountryData[],{"Name","Population"}];
view[n_]:=DynamicModule[
{}
, WordCloud[data[[;;-n]]]
, Initialization :> ("just to check something"; Print["initialization for input: ", n])
]
DynamicModule[{tab=1,n=1,nest=1},
Column[{
Grid[{{
"n: ", PopupMenu[Dynamic[n],Range[5]]
, "tab: ", SetterBar[Dynamic[tab],Range[10]]
, "nest: ", PopupMenu[Dynamic[nest],Range[5]]
}}, Alignment->{Center,Center}
]
,
HDView[
Join[
Table[With[{i=i},
{i, i -> Dynamic[view[n+i], TrackedSymbols :> {n}] }
(* view[n+i] and {n} can be whatever, `Dynamic` does not matter and do not pass there more options as they will be ignored anyway *)
(* consider this Dynamic[stuff, TrackedSymbols :> {__}] as a symbolic marker for HDView *)
], {i, 10} ]
, {{"static1", "static1" -> "whatever"} }
, {{"anotherDynamic", "anotherDynamic" -> Dynamic[Pause[1]; Nest[Framed,1,nest], TrackedSymbols:>{nest}]}} (*just an independent tab *)
]
, Dynamic[tab]
, "LayoutFunction" -> TabView (*can be PaneSelector or whatever to handle layoutFunction[{lbl->pane,...}, Dynamic[tag], automatically-filtered-options]*)
, "EventMonitor" -> Print
, ControlPlacement -> {Left, Top}
, ImageSize -> { Full, Full }
, Alignment -> {Center, Center}
]//Panel[#, "Test", ImageSize-> All{1,1}]&
}]
]
TabView[{1->1,"a"->"a"},Dynamic[tag]]
tag="a"