text: #!/usr/bin/env python3
  # -*- coding: utf-8 -*-
  """
  Created on Sun Jun 25 10:44:24 2017
  
  @author: wroscoe
  """
  
  import time
  from threading import Thread
  from .memory import Memory
  from .log import get_logger
  from donkeycar.util.loader import create_instance
  
  logger = get_logger(__name__)
  
  class Vehicle:
      def __init__(self, mem=None):
          if not mem:
              mem = Memory()
          self.mem = mem
          self.parts = []
          self.on = True
          self.threads = []
  
      def add(self, part, inputs=[], outputs=[],
              threaded=False, run_condition=None):
          """
          Method to add a part to the vehicle drive loop.
  
          Parameters
          ----------
              inputs : list
                  Channel names to get from memory.
              outputs : list
                  Channel names to save to memory.
              threaded : boolean
                  If a part should be run in a separate thread.
              run_condition: boolean
                  If a part should be run at all.
          """
  
          p = part
          logger.info('Adding part {}.'.format(p.__class__.__name__))
          entry = dict()
          entry['part'] = p
          entry['inputs'] = inputs
          entry['outputs'] = outputs
          entry['run_condition'] = run_condition
  
          if threaded:
              t = Thread(target=part.update, args=())
              t.daemon = True
              entry['thread'] = t
          self.parts.append(entry)
  
      def start(self, rate_hz=20, max_loop_count=None):
          """
          Start vehicle's main drive loop.
  
          This is the main thread of the vehicle. It starts all the new
          threads for the threaded parts then starts an infinit loop
          that runs each part and updates the memory.
  
          Parameters
          ----------
  
          rate_hz : int
              The max frequency that the drive loop should run. The actual
              frequency may be less than this if there are many blocking parts.
          max_loop_count : int
              Maxiumum number of loops the drive loop should execute. This is
              used for testing the all the parts of the vehicle work.
          """
  
          try:
              self.on = True
  
              for entry in self.parts:
                  if entry.get('thread'):
                      # start the update thread
                      entry.get('thread').start()
  
              # wait until the parts warm up.
              logger.info('Starting vehicle...')
              time.sleep(1)
  
              loop_count = 0
              while self.on:
                  start_time = time.time()
                  loop_count += 1
  
                  self.update_parts()
  
                  # stop drive loop if loop_count exceeds max_loopcount
                  if max_loop_count and loop_count > max_loop_count:
                      self.on = False
  
                  sleep_time = 1.0 / rate_hz - (time.time() - start_time)
                  if sleep_time > 0.0:
                      time.sleep(sleep_time)
  
          except KeyboardInterrupt:
              pass
          finally:
              self.stop()
  
      def update_parts(self):
          """
          loop over all parts
          """
          for entry in self.parts:
              # don't run if there is a run condition that is False
              run = True
              if entry.get('run_condition'):
                  run_condition = entry.get('run_condition')
                  run = self.mem.get([run_condition])[0]
                  # print('run_condition', entry['part'], entry.get('run_condition'), run)
  
              if run:
                  p = entry['part']
                  # get inputs from memory
                  inputs = self.mem.get(entry['inputs'])
  
                  # run the part
                  if entry.get('thread'):
                      outputs = p.run_threaded(*inputs)
                  else:
                      outputs = p.run(*inputs)
  
                  # save the output to memory
                  if outputs is not None:
                      self.mem.put(entry['outputs'], outputs)
  
      def stop(self):
          logger.info('Shutting down vehicle and its parts...')
          for entry in self.parts:
              try:
                  entry['part'].shutdown()
              except Exception as e:
                  logger.debug(e)
  
      def _get_pilot(self):
          for entry in self.parts:
              try:
                  if entry['part'].__subclasses__().__name__ is "KerasPilot":
                      return entry
              except:
                  pass
  
      def change_model(self, model_path, model):
          class_name = model.split('-', 1)[0]
          pilot = self._get_pilot()
          if pilot is not None and pilot['part'].__class__.__name__ is class_name:
              pilot['part'].load('{}{}'.format(model_path, model))
          else:
              if pilot in self.parts:
                  self.parts.remove(pilot)
              pilot = create_instance("donkeycar.parts.keras", class_name)
              pilot.load('{}{}'.format(model_path, model))
              self.add(pilot,
                  inputs=pilot.drive_inputs(),
                  outputs=['pilot/angle', 'pilot/throttle'],
                  run_condition='run_pilot')
  