UI: Rearchitect controllers and improve tracks performance

This CL involves several changes to the UI code. Major changes:

- Introduce a new architecture for controllers, strongly inspired
  by the frontend's VDOM architecture. Controllers are organized
  as hierarchical state machines. The root AppController is invoked
  every time there is a state update, or by internal events triggered
  by the controller themselves. Like in the frontend, every time
  something changes we re-"render" the all controller tree.
  On each pass, each controller can: (i) alter its current state;
  (ii) specify some children controllers. The runtime (controller.ts)
  takes care of handling lifetime of child controllers, ensuring they
  are only created when they are returned for the first time and
  destroyed when they are not returned anymore.

- Change the data format of tracks and make it more compact. Instead
  of sending one object per slice we now send slices in a columnar
  fashion, with one typed array per column.

- Stop sending the whole slices all the time. Instead, let the
  frontend dynamically ask for slices as we pan around (% some
  caching) and specify a minimum resolution, so that slices
  shorter than 1 pixel are filtered out at the source.

Change-Id: I6dfe88c5c469d906d3042b6ad5fe59d260c346ed
diff --git a/ui/src/common/state.ts b/ui/src/common/state.ts
index 935b7be..a57d979 100644
--- a/ui/src/common/state.ts
+++ b/ui/src/common/state.ts
@@ -31,6 +31,13 @@
   // chrome slice state:
   upid?: number;
   utid?: number;
+  dataReq?: TrackDataRequest;
+}
+
+export interface TrackDataRequest {
+  start: number;
+  end: number;
+  resolution: number;
 }
 
 export interface EngineConfig {
@@ -45,11 +52,20 @@
   query: string;
 }
 
-export interface PermalinkConfig { state: State; }
+export interface PermalinkConfig {
+  requestId?: string;  // Set by the frontend to request a new permalink.
+  hash?: string;       // Set by the controller when the link has been created.
+}
 
 export interface TraceTime {
   startSec: number;
   endSec: number;
+  lastUpdate: number;  // Epoch in seconds (Date.now() / 1000).
+}
+
+export interface Status {
+  msg: string;
+  timestamp: number;  // Epoch in seconds (Date.now() / 1000).
 }
 
 export interface State {
@@ -61,10 +77,12 @@
    */
   engines: ObjectById<EngineConfig>;
   traceTime: TraceTime;
+  visibleTraceTime: TraceTime;
   tracks: ObjectById<TrackState>;
   displayedTrackIds: string[];
   queries: ObjectById<QueryConfig>;
-  permalink: null|PermalinkConfig;
+  permalink: PermalinkConfig;
+  status: Status;
 }
 
 export function createEmptyState(): State {
@@ -72,10 +90,12 @@
     route: null,
     nextId: 0,
     engines: {},
-    traceTime: {startSec: 0, endSec: 10},
+    traceTime: {startSec: 0, endSec: 10, lastUpdate: 0},
+    visibleTraceTime: {startSec: 0, endSec: 10, lastUpdate: 0},
     tracks: {},
     displayedTrackIds: [],
     queries: {},
-    permalink: null,
+    permalink: {},
+    status: {msg: '', timestamp: 0},
   };
 }