Performance tuning of dynamic interfaces

Kuba Podkalicki (kuba.pod@gmail.com)
GluonVision GmBH

Prerequisites

tutorial/IntroductionToDynamic

tutorial/AdvancedDynamicFunctionality

Lou D’Andria’s WTC2018 talk: “Understanding DynamicModule”

FrontEnd team’s WTC talks

Outline

Basics

FE-side programming

Fast prototyping of complex interfaces

Bonuses

Warmup

Cell expressions / Boxes

button[1,foo[]]

Button[1,foo[]]

Nesting dynamics

Manipulate[
RegionPlot[Sin[x y]>.5,{x,y}∈Disk[{0,0},5],FrameStyle->Hue@c],
{c,0,1}
]

FrontEnd-side programming

LinkSnooper

CurrentValue[$FrontEnd,{EvaluatorNames,"LinkSnooper"}]={"AutoStartOnLaunch"->False,"MLOpenArguments"->"-LinkMode Launch -LinkName '\"`javaw`\" -classpath \"`jlinkjar`\" com.wolfram.jlink.util.LinkSnooper -kernelname \"`mathkernel`\" -noinit'"};
CurrentValue[EvaluationNotebook[],Evaluator]="LinkSnooper"

1

DynamicModule

x=1;
Table[Slider@Dynamic@x,200]

DynamicModule[{x},
Table[Slider@Dynamic@x,200]
]

Animator, FrontEnd contexts, FrontEnd capabilities

Framed[
  DynamicModule[{t = 0}
  , Overlay[
      {
        Animator[Dynamic@t,(*!!!*) AppearanceElements -> None, AnimationRepetitions -> Infinity
        , AnimationRate -> 1
        ]
      , Rotate[ 
Dynamic @ RawBoxes @ FEPrivate`ImportImage[          FrontEnd`ToFileName[{"Dialogs", "CloudDialogs"}, "ProgressSpinner.png"]        ],
 Dynamic[2 Pi t]]
      }
    , ImageSize -> {100, 100}, Alignment -> Center]
  ]
, Background -> Gray
]

While[True];

StyleSheet and dynamic warnings

tutorial/WorkingWithStylesheets

CellMargins->{{
   0.135 FrontEnd`AbsoluteCurrentValue[{WindowSize, 1}], 
   0.03 FrontEnd`AbsoluteCurrentValue[{WindowSize, 1}]}, 
  {1.5 Inherited, 1.5 Inherited}}

Creating version specific styles

(HyperlinkURL["https://mathematica.stackexchange.com/q/140965/5478", https://mathematica.stackexchange.com/q/140965/5478])

Cell[StyleData["Notebook"]
          , Background -> FEPrivate`If[
                FEPrivate`Less[ FEPrivate`$VersionNumber, 11.1]
              , RGBColor[1, 0, 0]
              , RGBColor[0, 1, 0]
            ]
        ]

Knowing FrontEnd limits

DynamicModule[{x},
{Slider@Dynamic[x],Dynamic[{x,x}]}
]

DynamicModule[{x},
{Slider@Dynamic[x],{Dynamic@x,Dynamic@x}}
]

Limited yet useful

Problem

Dynamic[{RandomReal[],CurrentValue[WindowSize]}]

Solution

DynamicModule[{cws},
DynamicWrapper[
Dynamic[{RandomReal[],cws}],
FEPrivate`Set[cws,CurrentValue[WindowSize]]
]
]

Approach to a state management and modularization of larger interfaces

What is a state management?

DynamicModule[{x=1, y= "s"},
Panel@Column@{
LabeledSlider @ Dynamic @ x,
InputField[Dynamic@y , String]
}
]

Basic modularization and state management

myGui[]:=DynamicModule[{value=1},
Panel@Column[{
"Title",
valueController[Dynamic@value],
"footer"
}]
]

valueController[spec:_Dynamic]:=Panel@Row[{LabeledSlider[spec],Graphics@Disk[]}]

myGui[]

Problems with scalling of that approach

Passing references around is cumbersome

Common container is not a soution for growing list of variables

DynamicModule[{x={"Kuba",175}},
Column[{
Slider[Dynamic@x[[2]],{1,200,1}],
Dynamic[{x[[1]],RandomReal[]}],
Dynamic[{x[[2]], RandomReal[]}(*,TrackedSymbols:>{...}*)]
}]
]

Understanding DynamicModule

aux

ParsedForm ~ SetAttributes ~HoldAllComplete;
ParsedForm[expr_]:=Block[{$ContextPath={},$Context="System`"},ToString@Unevaluated@expr];

EvaluatedForm[expr_]:=Block[{$ContextPath={},$Context="System`"},ToString@expr];

TypesetForm[expr_]:=Block[{$ContextPath={},$Context="System`"},ToString@DisplayForm@ToBoxes@ToBoxes@expr];

WrittenForm=Function[expt,
  Module[{nb2},
    nb2=CreateDocument[expt,Visible->False];
   (NotebookClose[nb2];EvaluatedForm@#)&@NotebookRead[First@Cells[nb2]][[1,1]]
  ],
  HoldAll
];

toInputCell=CellPrint[Cell[BoxData@#,"Input",Background->LightGray]]&;

The main loop recap

button[1,foo[]]

Button[1,foo[]]

DM’s scoping rough outline

Needs["GeneralUtilities`"];
GeneralUtilities`y;

DynamicModule[{x,y},Dynamic[{x,y}]]

DynamicModule[{x,y},Dynamic[{x,y}]]//ParsedForm//toInputCell
DynamicModule[{x,y},Dynamic[{x,y}]]//EvaluatedForm//toInputCell
DynamicModule[{x,y},Dynamic[{x,y}]]//TypesetForm//toInputCell
DynamicModule[{x,y},Dynamic[{x,y}]]//WrittenForm//toInputCell
DynamicModule[{x,y},Dynamic[{x,y}]] (*rendered form*)

Bonus: detailed lifecycle of DynamicModule with focus on scoping steps

I'm skipping System` symbols context after parsing step for a readability. Also, I'm skipping not relevant/obvious parts of the cycle. 

This is a rough example of what I understand, in reality more is done internally and exceptions may occur. I don't have insight to both 
so don't treat it like a precise documentation. 

User/FE		input			DynamicModule[{x = 1, f}, 
								f[]:=x++; 
								g[]:=x--; 
								{Button["inc", f[]], Dynamic[x]}, 
								SaveDefinitions -> True 
							]
							
(* reading boxes and sending to the kernel *)
					
Kernel		Parsing			DynamicModule[{Global`x = 1, Global`f}, 
								Global`f[]:= Global`x++; 
								Global`g[]:= Global`x--; 
								{Button["inc", Global`f[]], Dynamic[Global`x]}, 
								SaveDefinitions -> True 
							]
						
							Global` doesn't have to be Global` it is just a current $Context

Kernel		Evaluation		1. DynamicModule's head evaluation leads to the first step of scoping (Module style) 

							(*using `x instead of Global`x, for readability*)

							DynamicModule[{ `x$123 = 1, `f$123}, 
								`f$123[]:= `x$123++; 
								`g[]:= `x$123--; 
								{Button["inc", `f$123[]], Dynamic[`x$123]}, 
								SaveDefinitions -> True 
							]
						
							Additional renaming may occur if nested scoping constructs are present. Lookup "automatic renaming" online
						
							2. DynamicModule's body evaluation
						
								- local OwnValues generated in that step (e.g. x=123) are gradually moved to DM's first argument (e.g. {`x$123 =3, ...})
								  It is not the case in our example.
								- local values, other then OwnValues, are moved to the DynamicModuleValues option
								
								  DynamicModule[{ `x$123 = 1, `f$123}
								    , 
								  	{Button["inc", `f$123[]], Dynamic[`x$123]}, 
									SaveDefinitions -> True 
									DynamicModuleValues :> {
										{DownValues[`f$123] = {HoldPattern[`f$123[]] :> `x$123++}}
									}
								  ]
								  
							3. After "standard" steps of evaluation are done DynamicModule's Module style variables are replaced with original equivalents again.
						
								DynamicModule[{`x = 1, `f}, 
									{Button["inc", `g[]], Dynamic[`x]}, 
									DynamicModuleValues :> {{DownValues[`f] = {HoldPattern[`f[]] :> `x++}}}, 
									SaveDefinitions -> True
								]
							
Kernel		Writing			1. SaveDefinitions is handled, needed definitions are inserted in the Initialization of DynamicModule

							2. During convertion to boxes all DynamicModule's variables are renamed to name$$
	
								(*I'm dropping RowBox structure for readability *)
	
							DynamicModuleBox[{`x$$=1, `f$$},
								{ 
								    ButtonBox["inc", ButtonFunction :> `g[], Appearance->Automatic, Evaluator->Automatic, Method->"Preemptive"], 	
								    DynamicBox[ ToBoxes[x$$,StandardForm] ]
								},
								Initialization :> {
									g[]:=x$123--,
									Attributes[x$123]={Temporary},
									x$123=1
								},
								DynamicModuleValues :> {
									{DownValues[f$$] = {HoldPattern[f$$[]] :> x$$++}}
								}
							]
						
							We can already see here that `g[]` defined this way won't work. It contains a reference to x$123 which is not the same as x$$
							
 (*sending result to the FrontEnd *)
						
FrontEnd	Rewriting		All symbols from a current $Context or CellContext (Except System` - todo check), are rewritten to a $CellContext`. (Symbols, not symbol names in boxes' strings)

							EXAMPLES

							a) Here Global` is our $Context as well as current CellContext so we will get something like:
						
							  DynamicModuleBox[{$CellContext`x$$=1, $CellContext`f$$},
								{ 
								    ButtonBox["inc", ButtonFunction :> $CellContext`g[]
								    
							  Notice that `g` was renamed too, rule has no exceptions (known to me). That `g` is probably a reason why $CellContext` was introduced,
							  now you can change notebooks CellContext->Notebook and `g` will fire with whatever definition you will use there.
							 
						
							b) One can check more unintuitive example, set notebook context to unique to the notebook and use input starting with 
						
							  DynamicModule[{x = 1, Global`f},...	Created boxes will be of form 
						
							  DynamicModuleBox[{$CellContext`x$$=1, Global`f$$},
								{ 
								    ButtonBox["inc", ButtonFunction :> $CellContext`g[]
								    
							IMPLICATIONS
								f[]:=Print[1]
								DynamicModule[{x},Button[1,f[]]]
								NotebookWrite[nb,BoxData@ToBoxes@DynamicModule[{x},Button[1,f[]]]]
								if nb is an another notebook with a CellContext->Notebook then the Button won't work
								Global`f[]  there because it will call $CellContext`f[]
								
								
							  
(* last result is what is really written to the notebook file. In a text editor, all you can see is that *)


FrontEnd	FE`Scoping		When the output is displayed or the Kernel is restarted, the FrontEnd instantiates scoping within DynamicModuleBox,
							taking care about nested structures and InheritScope.
							
							Every DynamicModuleBox's symbol is associated with a FrontEnd variable. They are related by name:
							
							context`name$$ is associated with FE`context`name$$xx unless context is $CellContext`, then
							$CellContext`name$$ is associated with FE`name$$xx,
							
							where xx is something similar to $ModuleNumber, but in the front end.
					
							context`name$$ isn't really used so don't think that you can enter the DM scope by modifying e.g. Global`x$$.
							You have to know exactly FE`x$$43 name and it will work because those variables are shared by the FrontEnd and the Kernel, 
							unlike ordinary variables which are only living inside Kernel. This allows things like this work withoug calling the Kernel:
							
							DynamicModule[{x}, {Slider@Dynamic@x,Dynamic@x},UnsavedVariables:>{x}]
							
							I've added unsaved variables to show that FE really stores those values and not read/write from DynamicModuleBox first argument.
							
							IMPLICATIONS
							
							Dynamic[x$$] defined outside will work only if x is not found on $Context path during parsing of DynamicModule[{x}, definition.

Solution to reference passing problem

myGui2[]:=DynamicModule[{Store`x=1,mainLoading=True},
Dynamic[
If[
TrueQ @ mainLoading,
 ProgressIndicator[Appearance->"Percolate"],
myPanel[]],
TrackedSymbols:>{mainLoading}
],
SynchronousInitialization->False,
Initialization:>(Pause[2];mainLoading=False;)
];

myPanel[]:=Panel @ Column @  {
myController[], 
myPlot[],
invalidButton[],
validButton[]
}

myController[]:=Slider @ Dynamic @ Store`x$$;

myPlot[]:=Plot[Sin[x],{x,0,10},Frame->True,FrameStyle->Dynamic@Directive[Thick,Hue[Store`x$$]]]

invalidButton[]:=Button["invalid reset",invalidReset[]];
invalidReset[]:=Store`x$$=0;

validButton[]:=Button["valid reset",validReset[Store`x$$]];
validReset//Attributes = {HoldFirst};
validReset[var_]:=var=0;

myGui2[]

Button["valid reset",validReset[Store`x$$]]//TypesetForm//toInputCell

Solution summary

Pros and cons

no reference passing! fast prototyping, maintaining, fixing of the code

gui metaprogramming

steep learning curve

requires context control (it can be achieved without writing full names)

forces author to use meaningfull symbol names (that is a good habit anyway)

(HyperlinkURL["Internal DynamicModule steals scope of external DynamicModule", https://mathematica.stackexchange.com/q/116167/5478])

It is not a golden hammer, it is a solution to one problem, that is how to manage DynamicModule variables withouth a headache

Rules to remember

DynamicModule[{Store`x},{Dynamic@Store`x$$,Button["test", foo @ Store`x$$], ...}]

meaningful names

do not forget $$

Store`x$$ can only be parsed by the FrontEnd. Kernel can work with them but FE needs to prepare them. See valid/invalidButton code.

Bonus: 
DynamicObjects` - prototype solution for generating and controlling DynamicModule variables

(HyperlinkURL["StackExchange: scaling dynamic widgets up", https://mathematica.stackexchange.com/q/174792/5478])

init

Import["https://raw.githubusercontent.com/kubapod/mpm/master/install.m"]
Needs @ "MPM`"    
MPM`MPMInstall["kubapod", "dynamicobjects"]

Needs @ "DynamicObjects`";

n = 120;
names = Range[n];
pts = AssociationThread[names -> N@CirclePoints[n]];
edges = RandomSample[Subsets[names, {2}], 250];

Basic example

FrontEndModule[ 
  Graphics[
    { 
      { 
        Dynamic @ If[ 
          TrueQ[ FrontEndSymbol["state",#1] || FrontEndSymbol["state", #2] ]
        , Red
        , Black
        ]
      , Line[{pts[#1],pts[#2]}]
      }& @@@ edges    
    , {
        AbsolutePointSize@7
      , Dynamic @ If[ TrueQ[FrontEndSymbol["state",#1]], Red, Black]
      , EventHandler[Point@pts[#]
        , { "MouseEntered":>(FrontEndSymbol["state",#1]=True)
          , "MouseExited":>(FrontEndSymbol["state",#1]=False)
          }
        ]
      }& /@ names
    }
  , ImageSize -> Medium
  ]
]

The same example running completely FrontEnd-side

FrontEndModule[
 Graphics[
  { 
    { DynamicBox[
        FEPrivate`If[
          FEPrivate`SameQ[ FEPrivate`Or[ FrontEndSymbol["state",#1], FrontEndSymbol["state", #2]], True ]
        , RGBColor[1,0,1]
        , RGBColor[0. ,0. ,0. , .1]
        
        ]
      ]
    , Line[{pts[#1],pts[#2]}]
    }& @@@ edges
  , PointSize[0.025]
  , { 
      DynamicBox[
        FEPrivate`If[ 
          SameQ[FrontEndSymbol["state",#1],True]
        , RGBColor[1,0,1]
        , RGBColor[0,0,0]
        ]
      ]
    , EventHandler[Point@pts[#]
      , { "MouseEntered" :> FEPrivate`Set[FrontEndSymbol["state",#1],True]
        , "MouseExited":>FEPrivate`Set[FrontEndSymbol["state",#1],False]
        }
      ]
    }& /@ names
  }
, ImageSize->Large
],
Initialization :> ( Set[FrontEndSymbol["state", #],False] & /@ Range[n])
]

Thanks!