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