Class: Roby::GUI::PlanRebuilderWidget

Inherits:
Qt::Widget
  • Object
show all
Defined in:
lib/roby/gui/plan_rebuilder_widget.rb

Overview

This widget displays information about the event history in a list, allowing to switch between the “important events” in this history

Defined Under Namespace

Modules: ContextMenuHandler Classes: Snapshot

Constant Summary collapse

DEFAULT_REMOTE_POLL_PERIOD =
0.05

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(parent, plan_rebuilder) ⇒ PlanRebuilderWidget

Returns a new instance of PlanRebuilderWidget.



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/roby/gui/plan_rebuilder_widget.rb', line 59

def initialize(parent, plan_rebuilder)
    super(parent)
    @list = Qt::ListWidget.new(self)
    @list.extend ContextMenuHandler
    @layout = Qt::VBoxLayout.new(self)

    @layout.add_widget(@btn_create_display)
    @history = {}
    @logfile = nil # set by #open
    @plan_rebuilder = plan_rebuilder
    @current_plan = DRoby::RebuiltPlan.new
    @layout.add_widget(list)

    Qt::Object.connect(list, SIGNAL("currentItemChanged(QListWidgetItem*,QListWidgetItem*)"),
                       self, SLOT("currentItemChanged(QListWidgetItem*,QListWidgetItem*)"))
end

Instance Attribute Details

#current_planObject (readonly)

The current plan managed by the widget



20
21
22
# File 'lib/roby/gui/plan_rebuilder_widget.rb', line 20

def current_plan
  @current_plan
end

#historyObject (readonly)

The history, as a mapping from the cycle index to a (time, snapshot, list_item) triple



16
17
18
# File 'lib/roby/gui/plan_rebuilder_widget.rb', line 16

def history
  @history
end

#last_cycleInteger (readonly)

The last processed cycle

Returns:

  • (Integer)


26
27
28
# File 'lib/roby/gui/plan_rebuilder_widget.rb', line 26

def last_cycle
  @last_cycle
end

#listObject (readonly)

The list used to display all the cycles in the history



13
14
15
# File 'lib/roby/gui/plan_rebuilder_widget.rb', line 13

def list
  @list
end

#logfileDRoby::Logfile::Reader (readonly)

The underlying log file



23
24
25
# File 'lib/roby/gui/plan_rebuilder_widget.rb', line 23

def logfile
  @logfile
end

#plan_rebuilderObject (readonly)

The PlanRebuilder object we use to process the log data



18
19
20
# File 'lib/roby/gui/plan_rebuilder_widget.rb', line 18

def plan_rebuilder
  @plan_rebuilder
end

Class Method Details

.analyze(plan_rebuilder, logfile, until_cycle: nil) ⇒ Object



216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
# File 'lib/roby/gui/plan_rebuilder_widget.rb', line 216

def self.analyze(plan_rebuilder, logfile, until_cycle: nil)
    start_time, end_time = logfile.index.range

    start = Time.now
    puts "log file is #{(end_time - start_time).ceil}s long"
    dialog = Qt::ProgressDialog.new("Analyzing log file", "Quit", 0, (end_time - start_time))
    dialog.setWindowModality(Qt::WindowModal)
    dialog.show

    while !logfile.eof? && (!until_cycle || !plan_rebuilder.cycle_index || plan_rebuilder.cycle_index < until_cycle)
        data = logfile.load_one_cycle
        plan_rebuilder.process_one_cycle(data)
        if block_given?
            needs_snapshot =
                plan_rebuilder.has_structure_updates? ||
                plan_rebuilder.has_event_propagation_updates?
            yield(needs_snapshot, data)
        end
        plan_rebuilder.clear_integrated
        dialog.setValue(plan_rebuilder.cycle_start_time - start_time)
        if dialog.wasCanceled
            Kernel.raise Interrupt
        end
    end
    dialog.dispose
    puts format("analyzed log file in %.2fs", Time.now - start)
end

Instance Method Details

#add_missing_cycles(count) ⇒ Object



121
122
123
124
125
126
127
# File 'lib/roby/gui/plan_rebuilder_widget.rb', line 121

def add_missing_cycles(count)
    item = Qt::ListWidgetItem.new(list)
    item.setBackground(Qt::Brush.new(Qt::Color.fromHsv(33, 111, 255)))
    item.flags = Qt::NoItemFlags
    # NOTE: 'item' gets registered on the list by its constructor
    item.text = "[#{count} cycles missing]" # rubocop:disable Lint/UselessSetterCall
end

#analyze(until_cycle: nil) ⇒ Object



244
245
246
247
248
# File 'lib/roby/gui/plan_rebuilder_widget.rb', line 244

def analyze(until_cycle: nil)
    PlanRebuilderWidget.analyze(plan_rebuilder, logfile, until_cycle: until_cycle) do
        push_cycle
    end
end

#append_to_historyObject



131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/roby/gui/plan_rebuilder_widget.rb', line 131

def append_to_history
    snapshot = Snapshot.new plan_rebuilder.stats.dup,
                            DRoby::RebuiltPlan.new
    snapshot.plan.merge(plan_rebuilder.plan)
    if @last_snapshot
        snapshot.plan.dedupe(@last_snapshot.plan)
    end
    @last_snapshot = snapshot

    cycle = snapshot.stats[:cycle_index]
    time = Time.at(*snapshot.stats[:start]) + snapshot.stats[:actual_start]

    item = Qt::ListWidgetItem.new(list)
    item.text = "@#{cycle} - #{Roby.format_time(time)}"
    item.setData(Qt::UserRole, Qt::Variant.new(cycle))
    history[cycle] = [time, snapshot, item]
    emit addedSnapshot(cycle)
end

#apply(snapshot) ⇒ Object



158
159
160
161
162
163
# File 'lib/roby/gui/plan_rebuilder_widget.rb', line 158

def apply(snapshot)
    @display_time = Time.at(*snapshot.stats[:start]) + snapshot.stats[:end]
    @current_plan.clear
    @current_plan.merge(snapshot.plan)
    emit appliedSnapshot(Qt::DateTime.new(@display_time))
end

#connect(client, options = {}) ⇒ Object

Displays the data incoming from client

client is assumed to be a DRoby::Logfile::Client instance

update_period is, in seconds, the period at which the display will check whether there is new data on the port.



283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
# File 'lib/roby/gui/plan_rebuilder_widget.rb', line 283

def connect(client, options = {})
    options = Kernel.validate_options options,
                                      port: DRoby::Logfile::Server::DEFAULT_PORT,
                                      update_period: DEFAULT_REMOTE_POLL_PERIOD

    if client.respond_to?(:to_str)
        self.window_title = "roby-display: #{client}"
        emit sourceChanged

        begin
            hostname = client
            client = DRoby::Logfile::Client.new(client, options[:port])
        rescue Exception => e
            connection_failed(e, client, options)
            return false
        end
    end

    @client = client
    client.add_listener do |data|
        plan_rebuilder.clear_integrated
        plan_rebuilder.process_one_cycle(data)
        time = push_cycle
        emit liveUpdate(Qt::DateTime.new(time))

        cycle = plan_rebuilder.cycle_index
        time = plan_rebuilder.cycle_start_time
        emit info("@#{cycle} - #{time.strftime('%H:%M:%S.%3N')}")
    end
    @connection_pull = timer = Qt::Timer.new(self)
    timer.connect(SIGNAL("timeout()")) do
        begin
            client.read_and_process_pending(max: 0.1)
        rescue Exception => e
            disconnect
            emit warn("Disconnected: #{e.message}")
            puts e.message
            puts "  #{e.backtrace.join("\n  ")}"
            connect(hostname, options) if hostname
        end
    end
    timer.start(Integer(options[:update_period] * 1000))
    true
end

#connection_failed(e, client, options) ⇒ Object

Called when the connection to the log server failed, either because it has been closed or because creating the connection failed



253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
# File 'lib/roby/gui/plan_rebuilder_widget.rb', line 253

def connection_failed(e, client, options)
    @connection_error = e
    emit warn("connection failed: #{e.message}")
    if @reconnection_timer
        return
    end

    @reconnection_timer = Qt::Timer.new(self)
    @connect_client = client.dup
    @connect_options = options.dup
    @reconnection_timer.connect(SIGNAL("timeout()")) do
        puts "trying to reconnect to #{@connect_client} #{@connect_options}"
        if connect(@connect_client, @connect_options)
            emit info("Connected")
            @reconnection_timer.stop
            @reconnection_timer.dispose
            @reconnection_timer = nil
        end
    end
    @reconnection_timer.start(1000)
end

#current_timeObject

The end time of the last received cycle



345
346
347
# File 'lib/roby/gui/plan_rebuilder_widget.rb', line 345

def current_time
    plan_rebuilder.current_time
end

#currentItemChanged(new_item, previous_item) ⇒ Object



153
154
155
156
# File 'lib/roby/gui/plan_rebuilder_widget.rb', line 153

def currentItemChanged(new_item, previous_item)
    data = new_item.data(Qt::UserRole).toInt
    apply(history[data][1])
end

#cycle_start_timeObject



335
336
337
# File 'lib/roby/gui/plan_rebuilder_widget.rb', line 335

def cycle_start_time
    plan_rebuilder.cycle_start_time
end

#disconnectObject



328
329
330
331
332
333
# File 'lib/roby/gui/plan_rebuilder_widget.rb', line 328

def disconnect
    @client.disconnect
    @connection_pull.stop
    @connection_pull.dispose
    @connection_pull = nil
end

#display_timeObject

The time of the currently selected snapshot



350
351
352
# File 'lib/roby/gui/plan_rebuilder_widget.rb', line 350

def display_time
    @display_time || start_time
end

#job_placeholder_of(task, cycle) ⇒ Object

Returns the job information for the given task in the given cycle



112
113
114
115
116
117
118
119
# File 'lib/roby/gui/plan_rebuilder_widget.rb', line 112

def job_placeholder_of(task, cycle)
    if task.kind_of?(Roby::Interface::Job)
        _, snapshot, * = history[cycle]
        task
            .enum_parent_objects(snapshot.relations[Roby::TaskStructure::PlannedBy])
            .first
    end
end

#open(filename, index_path: nil) ⇒ Object

Opens filename and reads the data from there



204
205
206
207
208
209
210
211
212
# File 'lib/roby/gui/plan_rebuilder_widget.rb', line 204

def open(filename, index_path: nil)
    @logfile = DRoby::Logfile::Reader.open(filename, index_path: index_path)
    self.window_title = "roby-display: #{filename}"
    emit sourceChanged
    analyze
    unless history.empty?
        apply(history[history.keys.min][1])
    end
end

#push_cycle(snapshot: true) ⇒ Object



183
184
185
186
187
188
189
190
191
192
193
194
195
196
# File 'lib/roby/gui/plan_rebuilder_widget.rb', line 183

def push_cycle(snapshot: true)
    cycle = plan_rebuilder.stats[:cycle_index]
    if last_cycle && (cycle != last_cycle + 1)
        add_missing_cycles(cycle - last_cycle - 1)
    end
    needs_snapshot =
        plan_rebuilder.has_structure_updates? ||
        plan_rebuilder.has_event_propagation_updates?
    if snapshot && needs_snapshot
        append_to_history
    end
    @last_cycle = cycle
    Time.at(*plan_rebuilder.stats[:start]) + plan_rebuilder.stats[:actual_start]
end

#redraw(time = plan_rebuilder.current_time) ⇒ Object



199
200
201
# File 'lib/roby/gui/plan_rebuilder_widget.rb', line 199

def redraw(time = plan_rebuilder.current_time)
    emit appliedSnapshot(Qt::DateTime.new(time))
end

#seek(time) ⇒ Object



165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/roby/gui/plan_rebuilder_widget.rb', line 165

def seek(time)
    # Convert from QDateTime to allow seek() to be a slot
    if time.kind_of?(Qt::DateTime)
        time = Time.at(Float(time.toMSecsSinceEpoch) / 1000)
    end

    result = nil
    history.each_value do |cycle_time, snapshot, item|
        if (cycle_time < time) && (!result || result[0] < cycle_time)
            result = [cycle_time, snapshot]
        end
    end
    if result
        apply(result[1])
    end
end

#start_timeObject

The start time of the first received cycle



340
341
342
# File 'lib/roby/gui/plan_rebuilder_widget.rb', line 340

def start_time
    plan_rebuilder.start_time
end

#tasks_info(Set<Roby::Task>,Hash<Roby::Task,Roby::Task>)

Info about all tasks known within the stored history

Returns:

  • ((Set<Roby::Task>,Hash<Roby::Task,Roby::Task>))

    returns the set of all tasks stored in the history, as well as a mapping from job placeholder tasks to the corresponding job task



81
82
83
84
85
86
87
88
89
90
# File 'lib/roby/gui/plan_rebuilder_widget.rb', line 81

def tasks_info
    all_tasks = Set.new
    all_job_info = {}
    history.each_key do |cycle_index|
        tasks, job_info = tasks_info_of_snapshot(cycle_index)
        all_tasks.merge(tasks)
        all_job_info.merge!(job_info)
    end
    [all_tasks, all_job_info]
end

#tasks_info_of_snapshot(cycle) ⇒ Set<Roby::Task>

Returns the set of tasks that are present in the given snapshot

Returns:



95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/roby/gui/plan_rebuilder_widget.rb', line 95

def tasks_info_of_snapshot(cycle)
    _, snapshot, * = history[cycle]
    tasks = snapshot.plan.tasks.to_set
    job_info = {}
    tasks.each do |t|
        if t.kind_of?(Roby::Interface::Job)
            planned_by_graph = snapshot.plan.task_relation_graph_for(Roby::TaskStructure::PlannedBy)
            placeholder_task = planned_by_graph.enum_for(:each_out_neighbour, t).first
            if placeholder_task
                job_info[placeholder_task] = t
            end
        end
    end
    [tasks, job_info]
end