1// Copyright (c) 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5'use strict';
6
7/**
8 * @fileoverview Provides the ObjectSnapshot and ObjectHistory classes.
9 */
10base.require('base.range');
11base.require('base.sorted_array_utils');
12base.require('tracing.trace_model.object_snapshot');
13
14base.exportTo('tracing.trace_model', function() {
15  var ObjectSnapshot = tracing.trace_model.ObjectSnapshot;
16
17  /**
18   * An object with a specific id, whose state has been snapshotted several
19   * times.
20   *
21   * @constructor
22   */
23  function ObjectInstance(parent, id, category, name, creationTs) {
24    this.parent = parent;
25    this.id = id;
26    this.category = category;
27    this.name = name;
28    this.creationTs = creationTs;
29    this.creationTsWasExplicit = false;
30    this.deletionTs = Number.MAX_VALUE;
31    this.deletionTsWasExplicit = false;
32    this.selected = false;
33    this.colorId = 0;
34    this.bounds = new base.Range();
35    this.snapshots = [];
36    this.hasImplicitSnapshots = false;
37  }
38
39  ObjectInstance.prototype = {
40    __proto__: Object.prototype,
41
42    get typeName() {
43      return this.name;
44    },
45
46    addSnapshot: function(ts, args) {
47      if (ts < this.creationTs)
48        throw new Error('Snapshots must be >= instance.creationTs');
49      if (ts >= this.deletionTs)
50        throw new Error('Snapshots cannot be added after ' +
51                        'an objects deletion timestamp.');
52
53      var lastSnapshot;
54      if (this.snapshots.length > 0) {
55        lastSnapshot = this.snapshots[this.snapshots.length - 1];
56        if (lastSnapshot.ts == ts)
57          throw new Error('Snapshots already exists at this time!');
58        if (ts < lastSnapshot.ts) {
59          throw new Error(
60              'Snapshots must be added in increasing timestamp order');
61        }
62      }
63
64      var snapshotConstructor =
65          tracing.trace_model.ObjectSnapshot.getConstructor(this.name);
66      var snapshot = new snapshotConstructor(this, ts, args);
67      this.snapshots.push(snapshot);
68      return snapshot;
69    },
70
71    wasDeleted: function(ts) {
72      var lastSnapshot;
73      if (this.snapshots.length > 0) {
74        lastSnapshot = this.snapshots[this.snapshots.length - 1];
75        if (lastSnapshot.ts > ts)
76          throw new Error(
77              'Instance cannot be deleted at ts=' +
78              ts + '. A snapshot exists that is older.');
79      }
80      this.deletionTs = ts;
81      this.deletionTsWasExplicit = true;
82    },
83
84    /**
85     * See ObjectSnapshot constructor notes on object initialization.
86     */
87    preInitialize: function() {
88      for (var i = 0; i < this.snapshots.length; i++)
89        this.snapshots[i].preInitialize();
90    },
91
92    /**
93     * See ObjectSnapshot constructor notes on object initialization.
94     */
95    initialize: function() {
96      for (var i = 0; i < this.snapshots.length; i++)
97        this.snapshots[i].initialize();
98    },
99
100    getSnapshotAt: function(ts) {
101      if (ts < this.creationTs) {
102        if (this.creationTsWasExplicit)
103          throw new Error('ts must be within lifetime of this instance');
104        return this.snapshots[0];
105      }
106      if (ts > this.deletionTs)
107        throw new Error('ts must be within lifetime of this instance');
108
109      var snapshots = this.snapshots;
110      var i = base.findLowIndexInSortedIntervals(
111          snapshots,
112          function(snapshot) { return snapshot.ts; },
113          function(snapshot, i) {
114            if (i == snapshots.length - 1)
115              return snapshots[i].objectInstance.deletionTs;
116            return snapshots[i + 1].ts - snapshots[i].ts;
117          },
118          ts);
119      if (i < 0) {
120        // Note, this is a little bit sketchy: this lets early ts point at the
121        // first snapshot, even before it is taken. We do this because raster
122        // tasks usually post before their tile snapshots are dumped. This may
123        // be a good line of code to re-visit if we start seeing strange and
124        // confusing object references showing up in the traces.
125        return this.snapshots[0];
126      }
127      if (i >= this.snapshots.length)
128        return this.snapshots[this.snapshots.length - 1];
129      return this.snapshots[i];
130    },
131
132    updateBounds: function() {
133      this.bounds.reset();
134      this.bounds.addValue(this.creationTs);
135      if (this.deletionTs != Number.MAX_VALUE)
136        this.bounds.addValue(this.deletionTs);
137      else if (this.snapshots.length > 0)
138        this.bounds.addValue(this.snapshots[this.snapshots.length - 1].ts);
139    },
140
141    shiftTimestampsForward: function(amount) {
142      this.creationTs += amount;
143      if (this.deletionTs != Number.MAX_VALUE)
144        this.deletionTs += amount;
145      this.snapshots.forEach(function(snapshot) {
146        snapshot.ts += amount;
147      });
148    }
149  };
150
151  ObjectInstance.nameToConstructorMap_ = {};
152  ObjectInstance.register = function(name, constructor) {
153    if (ObjectInstance.nameToConstructorMap_[name])
154      throw new Error('Constructor already registerd for ' + name);
155    ObjectInstance.nameToConstructorMap_[name] = constructor;
156  };
157
158  ObjectInstance.unregister = function(name) {
159    delete ObjectInstance.nameToConstructorMap_[name];
160  };
161
162  ObjectInstance.getConstructor = function(name) {
163    if (ObjectInstance.nameToConstructorMap_[name])
164      return ObjectInstance.nameToConstructorMap_[name];
165    return ObjectInstance;
166  };
167
168  return {
169    ObjectInstance: ObjectInstance
170  };
171});
172