Exemplo n.º 1
0
	def __init__(self, config):
		super(DanceSurface, self).__init__()
		# Create the floor communication object
		self._init_comms(config)
		# Create the layout object and calculate floor size
		self.layout = DisplayLayout(config["modules"])
		(self.width, self.height) = self.layout.calculate_floor_size()
		self.total_pixels = self.width * self.height
		# Initialise all the pixels to black 
		self.pixels = [ 0 for n in range(0, 3*self.total_pixels) ]
		self.text_writer = TextWriter()
Exemplo n.º 2
0
    def run_ddrpi(self):
        self.logger.info("Starting DDRPi running at %s" % (time.strftime('%H:%M:%S %d/%m/%Y %Z')))
        config = self.parse_commandline_arguments()
        self.logger.info("%s" % config)

        # Initialise pygame, which we use for the GUI, controllers, rate limiting etc...
        pygame.init()
        # Set up a list of output endpoints, and input adapaters
        output_devices = []
        input_adapters = []

        # Parse the floor layout
        layout = DisplayLayout(config)
        converter = layout.get_converter()

        # Create a suitably sized canvas for the given config
        canvas = FloorCanvas(layout.size_x, layout.size_y)

        # Create a menu object to handle user input
        menu = Menu()

        output_filters = []
        if ("system" in config and "filters" in config["system"]):
            print ("Found filters config: %s" % config["system"]["filters"])
            for filter_number, filter_config in sorted(config["system"]["filters"].items()):
                if ("name" in filter_config and filter_config["name"] == "ClearFilter"):
                    output_filters.append(ClearFilter(filter_config))
                elif ("name" in filter_config and filter_config["name"] == "NegativeFilter"):
                    output_filters.append(NegativeFilter(filter_config))
                elif ("name" in filter_config and filter_config["name"] == "NeutralDensityFilter"):
                    output_filters.append(NeutralDensityFilter(filter_config))

        # Set up the various outputs defined in the config.
        # Known types are the moment are "gui", "serial" and "pipe"
        if ("outputs" in config):
            for output_number, details in config["outputs"].items():
                self.logger.info("%d - %s" % (output_number, details))
                if "enabled" in details:
                    if details["enabled"] is False:
                        self.logger.info("Skipping disabled output %d" % output_number)
                        self.logger.info("%s" % details)
                        continue
                self.logger.info("Configuring output %d" % output_number)
                if details["type"] == "serial":
                    self.logger.info("Creating a SerialOutput class")
                    serial_output = SerialOutput(details)
                    serial_output.set_name("SerialOutput-#%d" % output_number)
                    serial_output.set_output_converter(converter)
                    for output_filter in output_filters:
                        serial_output.append_filter(output_filter)
                    output_devices.append(serial_output)
                elif details["type"] == "gui":
                    # Skip the gui if headless has been specified
                    if "headless" in config["system"] and config["system"]["headless"] is True:
                        continue
                    self.logger.info("Creating a GuiOutput class [There can be only one]")
                    gui_output = GuiOutput()
                    output_devices.append(gui_output)
                    self.gui = gui_output
                elif details["type"] == "pipe":
                    self.logger.info("Creating a PipeOutput class")
                    pipe_output = PipeOutput(details)
                    pipe_output.set_output_converter(converter)
                    output_devices.append(pipe_output)
                else:
                    self.logger.warn("I don't know how to handle an output of type '%s'" % (details["type"]))

        # Initialise any connected joypads/joysticks
        controllers = self.init_joysticks()

        # There can be many plugin directories, and can either be relative (from the directory in which this
        #  script resides), or absolute. All directories are scanned, and the plugins found are added to the
        #  master list. If there are duplicate class names, the last one encountered probably wins silently.
        visualisation_plugin_dirs = ["visualisation_plugins", "game_plugins"]

        # We will store a dict of classname > class object in here
        available_plugins = self.load_plugins(visualisation_plugin_dirs)

        # Add all the available plugins to the menu
        #menu.add_available_plugins(available_plugins)

        # Create a data model that we can use here, and pass to the
        #  menu class to change what is active
        # We should also be able to provide this model to a webservice
        #  class if we chose to add in HTTP control of the floor too
        plugin_model = PluginModel((layout.size_x, layout.size_y))

        # Populate the data model
        plugin_model.add_plugins(available_plugins)

        # Link it up to the menu
        menu.set_plugin_model(plugin_model)

        # If we have defined a specific plugin to run indefinitely on the command line, use that
        specific_plugin = False
        if "plugin" in config["system"]:
            only_plugin = config["system"]["plugin"]
            if plugin_model.set_current_plugin(only_plugin) is not None:
                self.logger.info("Running requested plugin %s" % only_plugin)
                specific_plugin = True
            else:
                self.logger.info("Unable to find requested plugin: %s" % (only_plugin))
                self.logger.info("Available plugins are:")
                for available_plugin in available_plugins:
                    self.logger.info("  %s" % (available_plugin))
                # This is intended as a development debug option, or at the very least the
                #  person using it should know what they are doing, so if it isn't available
                #  for whatever reason, exit.
                exit()

        if "playlist" in config["system"]:

            # Retrieve the list of playlists specified on the command line
            #  or config file
            playlist_list = config["system"]["playlist"]
            self.logger.info(playlist_list)
            if len(playlist_list) > 0:
                for playlist in playlist_list:
                    # Make the playlist an absolute path if it isn't already
                    if not os.path.isabs(playlist):
                        root_directory = os.path.dirname(os.path.realpath(__file__))
                        playlist = os.path.join(root_directory, playlist)
                    # Load the playlist
                    plugin_model.add_playlist_from_file(playlist)
                # Start the first one we added
                self.logger.info("Setting current playlist to the first one we added")
                plugin_model.set_current_playlist_by_index(1)

        if "twitter" in config:

            if "enabled" in config["twitter"] and config["twitter"]["enabled"] is not True:
                # Don't do anything, we don't want it at the moment
                pass

            else:

                # Only bother importing this module if we are going to use it, hence not requiring
                #  an extra dependency on a Twitter library if people don't want to use that
                from lib.twitter import TwitterPlaylist

                # Get the twitter credentials from the configuration file:
                twitter_details = config["twitter"]

                # If we want to limit which plugins can be selected by Twitter, do so here
                plugin_whitelist = available_plugins

                # Create the twitter plugin playlist
                twitter_playlist = TwitterPlaylist(plugins=plugin_whitelist, details=twitter_details)

                playlist_index = plugin_model.add_playlist(twitter_playlist)
                if plugin_model.get_current_playlist() is None:
                    plugin_model.set_current_playlist_by_index(playlist_index)

        # When there is no plugin specified and no user playlists either, make the first
        #  playlist active, and select either the first plugin, or, if it is available,
        #  any of the plugins listed in DEFAULT_STARTUP_PLUGIN

        # .set_current_plugin() is a convenience method to start the all_plugins playlist
        #  with the given plugin name
        self.logger.info("Current playlist: %s" % plugin_model.get_current_playlist())
        have_started_a_plugin = False
        if plugin_model.get_current_playlist() is None:
            for specified_plugin in self.DEFAULT_STARTUP_PLUGIN:
                if plugin_model.set_current_plugin(specified_plugin) is not None:
                    have_start_a_plugin = True
                    self.logger.info("Started default plugin %s" % specified_plugin)
                    break

            # If we still haven't started anything, just start the all_plugins playlist
            if have_started_a_plugin is False:
                plugin_model.set_current_playlist_by_index(0)

        print (plugin_model)
        #
        if self.gui is not None:
            #playlistModel.add_model_changed_listener(self.gui)
            #			self.gui.set_playlist_model(playlistModel)
            self.gui.set_plugin_model(plugin_model)


        # Create an object that can map key events to joystick events
        self.controller_mapper = ControllerInput()

        # The main loop is an event loop, with each part
        #  non-blocking and yields after doing a short bit.
        # Each 'bit' is a frame

        # Check for pygame events, primarily coming from
        #  gamepads and the keyboard

        running = True
        while running:

            current_playlist = plugin_model.get_current_playlist()
            current_plugin = None
            if current_playlist is not None:
                current_plugin = current_playlist.get_current_plugin()

            for e in pygame.event.get():

                if e.type == pygame.QUIT:
                    running = False

                e = self.controller_mapper.map_event(e)

                # Each consumer should return None if the
                #  event has been consumed, or return
                #  the event if something else should
                #  act upon it

                # Check first if the framework is going to
                #  consume this event. This includes heading
                #  into menus, possibly modifying the plugin
                #  model (previous/next plugin)
                #e = self.handle_event(e)
                #if e is None:
                #	continue

                # See if the menu wants to consume this event
                e = menu.handle_event(e)
                if e is None:
                    continue

                #self.print_input_event(e)

                # Next pass it on to the current plugin, if
                #  there is one
                if current_plugin is not None:
                    #					e = current_plugin['instance'].handle_event(e)
                    e = current_plugin.instance.handle_event(e)
                    if e is None:
                        continue

            # Ask the framework if it thinks it is displaying something
            # display_frame = self.draw_frame(canvas)
            # Ask the menu if it wants to draw something
            display_frame = menu.draw_frame(canvas)
            if display_frame is None:
                # If there is nothing to draw from the framework, ask
                #  the current plugin to do something is there is one
                if current_plugin is not None:
                    #					display_frame = current_plugin['instance'].draw_frame(canvas)
                    # There is every chance that the plugin might throw an
                    #  exception as we have no control over the quality of
                    #  external code (or some in-house code!), so catch any
                    #  exception
                    try:
                        display_frame = current_plugin.instance.draw_frame(canvas)
                    except Exception as e:
                        self.logger.warn("Current plugin threw an error whilst running draw_frame()")
                        self.logger.warn(e)
                        display_frame = self.draw_error(canvas)

                else:
                    # If there is no plugin, then the framework should
                    #  do something
                    pass

            # Send the data to all configured outputs if there is any to send
            # TODO: If the plugin doesn't return a canvas for some reason,
            #        then the gui should be updated with a canvas, but the output
            #        plugins need not be informed. The problem is that if the plugin
            #        happens to never return anything, then the gui is never updated
            if display_frame is None:
                canvas.set_colour((0, 0, 0))
                display_frame = canvas

            for output_device in output_devices:
                output_device.send_data(display_frame)

            # Limit the framerate, we need not do it in the plugins - they really shouldn't
            #  mind that we are running at a max of 25fps
            self.clock.tick(25)

        pygame.quit()
        exit()
Exemplo n.º 3
0
class DanceSurface(object):
	"""
	The class representing the drawable dance floor. This is a wrapper around
	an internal representation of the dance floor, so that plugins need only
	write images using (x,y) coordinates and don't have to worry about the
	configuration of dance floor tiles. The dance surface is passed to the
	display plugins, and reacts to any changes made by sending the the
	appropriate updates to the dance floor through the serial port.
	"""
	def __init__(self, config):
		super(DanceSurface, self).__init__()
		# Create the floor communication object
		self._init_comms(config)
		# Create the layout object and calculate floor size
		self.layout = DisplayLayout(config["modules"])
		(self.width, self.height) = self.layout.calculate_floor_size()
		self.total_pixels = self.width * self.height
		# Initialise all the pixels to black 
		self.pixels = [ 0 for n in range(0, 3*self.total_pixels) ]
		self.text_writer = TextWriter()
		
	def _init_comms(self, config):
		"""
		Initialise the comms to the floor or simulator
		"""
		self.comms = ComboComms(config)

	def blit(self):
		"""
		Draw the updated floor to the serial port
		"""
		i = 0
		sanitised_pixels = []
		while i < len(self.pixels):
			p = self.pixels[i]
			if p < 0:
				logging.error("DanceSurface: Tried to send a pixel component with a negative value")
				sanitised_pixels.append(0)
			elif p > 255:
				logging.error("DanceSurface: Tried to send a pixel component with a value > 255")
				sanitised_pixels.append(255)
			else:
				sanitised_pixels.append(p)
			i += 1
		self.pixels = sanitised_pixels
		self.comms.send_data(self.pixels)

	def clear_hex(self, colour):
		"""
		Clear the surface to a single colour
		"""
		# Make sure we never send a 1 by mistake and screw up the frames
		(r,g,b) = [ v if not v == 1 else 0 for v in ColourUtils.hexToTuple(colour) ]
		for x in range(0,self.total_pixels):
			self.pixels[x*3:(x+1)*3] = [r,g,b]
			
	def clear_tuple(self, colour):
		"""
		Clear the surface to a single colour
		"""
		# Make sure we never send a 1 by mistake and screw up the frames
		(r,g,b) = [ v if not v == 1 else 0 for v in colour ]
		for x in range(0,self.total_pixels):
			self.pixels[x*3:(x+1)*3] = [r,g,b]

	def draw_hex_pixel(self, x, y, colour):
		"""
		Set the value of the pixel at (x,y) to colour(#RRGGBB")
		"""
		# Make sure we never send a 1 by mistake and screw up the frames
		(r,g,b) = [ v if not v == 1 else 0 for v in ColourUtils.hexToTuple(colour) ]
		pos = self.layout.get_position(x,y)
		if pos is not None:
			mapped_pixel = 3 * pos
			self.pixels[mapped_pixel:mapped_pixel+3] = [r,g,b]
	
	def draw_tuple_pixel(self, x, y, colour):
		"""
		Set the value of the pixel at (x,y) to colour((r,g,b))
		"""
		# Make sure we never send a 1 by mistake and screw up the frames
		(r,g,b) = [ v if not v == 1 else 0 for v in colour ]
		pos = self.layout.get_position(x,y)
		if pos is not None:
			mapped_pixel = 3 * pos
			self.pixels[mapped_pixel:mapped_pixel+3] = [r,g,b]
			
	def draw_float_tuple_pixel(self, x, y, colour):
		"""
		Set the value of the pixel at (x,y) to colour((r,g,b)) where r g and b are floats
		"""
		(floatR, floatG, floatB) = colour
		intR = int(floatR*255)
		intG = int(floatG*255)
		intB = int(floatB*255)
		self.draw_tuple_pixel(x, y, (intR, intG, intB))
			
	def draw_tuple_box(self, top_left, bottom_right, colour):
		"""
		Fill the box from top left to bottom right with the given colour
		"""
		(tlx,tly) = top_left
		(brx,bry) = bottom_right
		if tlx <= brx and tly <= bry:
			y = tly
			while y <= bry:
				x = tlx
				while x <= brx:
					self.draw_tuple_pixel(x, y, colour)
					x += 1
				y += 1

	def draw_text(self, text, colour, x_pos, y_pos):
		if (self.text_writer == None):
			return (0,0)
		# Returns the text size as a (width, height) tuple for reference
		text_size = self.text_writer.draw_text(self, text, colour, x_pos, y_pos)
		return text_size

	def get_text_size(self, text):
		if (self.text_writer == None):
			return (0,0)
		# Returns the text size as a (width, height) tuple for reference,
		#  but doesn't actually draw anything because it doesn't pass a surface through
		return self.text_writer.draw_text(None, text, (0,0,0), 0, 0)
Exemplo n.º 4
0
    def run_ddrpi(self):
        self.logger.info("Starting DDRPi running at %s" %
                         (time.strftime('%H:%M:%S %d/%m/%Y %Z')))
        config = self.parse_commandline_arguments()
        self.logger.info("%s" % config)

        # Initialise pygame, which we use for the GUI, controllers, rate limiting etc...
        pygame.init()
        # Set up a list of output endpoints, and input adapaters
        output_devices = []
        input_adapters = []

        # Parse the floor layout
        layout = DisplayLayout(config)
        converter = layout.get_converter()

        # Create a suitably sized canvas for the given config
        canvas = FloorCanvas(layout.size_x, layout.size_y)

        # Create a menu object to handle user input
        menu = Menu()

        output_filters = []
        if ("system" in config and "filters" in config["system"]):
            print("Found filters config: %s" % config["system"]["filters"])
            for filter_number, filter_config in sorted(
                    config["system"]["filters"].items()):
                if ("name" in filter_config
                        and filter_config["name"] == "ClearFilter"):
                    output_filters.append(ClearFilter(filter_config))
                elif ("name" in filter_config
                      and filter_config["name"] == "NegativeFilter"):
                    output_filters.append(NegativeFilter(filter_config))
                elif ("name" in filter_config
                      and filter_config["name"] == "NeutralDensityFilter"):
                    output_filters.append(NeutralDensityFilter(filter_config))

        # Set up the various outputs defined in the config.
        # Known types are the moment are "gui", "serial" and "pipe"
        if ("outputs" in config):
            for output_number, details in config["outputs"].items():
                self.logger.info("%d - %s" % (output_number, details))
                if "enabled" in details:
                    if details["enabled"] is False:
                        self.logger.info("Skipping disabled output %d" %
                                         output_number)
                        self.logger.info("%s" % details)
                        continue
                self.logger.info("Configuring output %d" % output_number)
                if details["type"] == "serial":
                    self.logger.info("Creating a SerialOutput class")
                    serial_output = SerialOutput(details)
                    serial_output.set_name("SerialOutput-#%d" % output_number)
                    serial_output.set_output_converter(converter)
                    for output_filter in output_filters:
                        serial_output.append_filter(output_filter)
                    output_devices.append(serial_output)
                elif details["type"] == "gui":
                    # Skip the gui if headless has been specified
                    if "headless" in config[
                            "system"] and config["system"]["headless"] is True:
                        continue
                    self.logger.info(
                        "Creating a GuiOutput class [There can be only one]")
                    gui_output = GuiOutput()
                    output_devices.append(gui_output)
                    self.gui = gui_output
                elif details["type"] == "pipe":
                    self.logger.info("Creating a PipeOutput class")
                    pipe_output = PipeOutput(details)
                    pipe_output.set_output_converter(converter)
                    output_devices.append(pipe_output)
                else:
                    self.logger.warn(
                        "I don't know how to handle an output of type '%s'" %
                        (details["type"]))

        # Initialise any connected joypads/joysticks
        controllers = self.init_joysticks()

        # There can be many plugin directories, and can either be relative (from the directory in which this
        #  script resides), or absolute. All directories are scanned, and the plugins found are added to the
        #  master list. If there are duplicate class names, the last one encountered probably wins silently.
        visualisation_plugin_dirs = ["visualisation_plugins", "game_plugins"]

        # We will store a dict of classname > class object in here
        available_plugins = self.load_plugins(visualisation_plugin_dirs)

        # Add all the available plugins to the menu
        #menu.add_available_plugins(available_plugins)

        # Create a data model that we can use here, and pass to the
        #  menu class to change what is active
        # We should also be able to provide this model to a webservice
        #  class if we chose to add in HTTP control of the floor too
        plugin_model = PluginModel((layout.size_x, layout.size_y))

        # Populate the data model
        plugin_model.add_plugins(available_plugins)

        # Link it up to the menu
        menu.set_plugin_model(plugin_model)

        # If we have defined a specific plugin to run indefinitely on the command line, use that
        specific_plugin = False
        if "plugin" in config["system"]:
            only_plugin = config["system"]["plugin"]
            if plugin_model.set_current_plugin(only_plugin) is not None:
                self.logger.info("Running requested plugin %s" % only_plugin)
                specific_plugin = True
            else:
                self.logger.info("Unable to find requested plugin: %s" %
                                 (only_plugin))
                self.logger.info("Available plugins are:")
                for available_plugin in available_plugins:
                    self.logger.info("  %s" % (available_plugin))
                # This is intended as a development debug option, or at the very least the
                #  person using it should know what they are doing, so if it isn't available
                #  for whatever reason, exit.
                exit()

        if "playlist" in config["system"]:

            # Retrieve the list of playlists specified on the command line
            #  or config file
            playlist_list = config["system"]["playlist"]
            self.logger.info(playlist_list)
            if len(playlist_list) > 0:
                for playlist in playlist_list:
                    # Make the playlist an absolute path if it isn't already
                    if not os.path.isabs(playlist):
                        root_directory = os.path.dirname(
                            os.path.realpath(__file__))
                        playlist = os.path.join(root_directory, playlist)
                    # Load the playlist
                    plugin_model.add_playlist_from_file(playlist)
                # Start the first one we added
                self.logger.info(
                    "Setting current playlist to the first one we added")
                plugin_model.set_current_playlist_by_index(1)

        if "twitter" in config:

            if "enabled" in config["twitter"] and config["twitter"][
                    "enabled"] is not True:
                # Don't do anything, we don't want it at the moment
                pass

            else:

                # Only bother importing this module if we are going to use it, hence not requiring
                #  an extra dependency on a Twitter library if people don't want to use that
                from lib.twitter import TwitterPlaylist

                # Get the twitter credentials from the configuration file:
                twitter_details = config["twitter"]

                # If we want to limit which plugins can be selected by Twitter, do so here
                plugin_whitelist = available_plugins

                # Create the twitter plugin playlist
                twitter_playlist = TwitterPlaylist(plugins=plugin_whitelist,
                                                   details=twitter_details)

                playlist_index = plugin_model.add_playlist(twitter_playlist)
                if plugin_model.get_current_playlist() is None:
                    plugin_model.set_current_playlist_by_index(playlist_index)

        # When there is no plugin specified and no user playlists either, make the first
        #  playlist active, and select either the first plugin, or, if it is available,
        #  any of the plugins listed in DEFAULT_STARTUP_PLUGIN

        # .set_current_plugin() is a convenience method to start the all_plugins playlist
        #  with the given plugin name
        self.logger.info("Current playlist: %s" %
                         plugin_model.get_current_playlist())
        have_started_a_plugin = False
        if plugin_model.get_current_playlist() is None:
            for specified_plugin in self.DEFAULT_STARTUP_PLUGIN:
                if plugin_model.set_current_plugin(
                        specified_plugin) is not None:
                    have_start_a_plugin = True
                    self.logger.info("Started default plugin %s" %
                                     specified_plugin)
                    break

            # If we still haven't started anything, just start the all_plugins playlist
            if have_started_a_plugin is False:
                plugin_model.set_current_playlist_by_index(0)

        print(plugin_model)
        #
        if self.gui is not None:
            #playlistModel.add_model_changed_listener(self.gui)
            #			self.gui.set_playlist_model(playlistModel)
            self.gui.set_plugin_model(plugin_model)

        # Create an object that can map key events to joystick events
        self.controller_mapper = ControllerInput()

        # The main loop is an event loop, with each part
        #  non-blocking and yields after doing a short bit.
        # Each 'bit' is a frame

        # Check for pygame events, primarily coming from
        #  gamepads and the keyboard

        running = True
        while running:

            current_playlist = plugin_model.get_current_playlist()
            current_plugin = None
            if current_playlist is not None:
                current_plugin = current_playlist.get_current_plugin()

            for e in pygame.event.get():

                if e.type == pygame.QUIT:
                    running = False

                e = self.controller_mapper.map_event(e)

                # Each consumer should return None if the
                #  event has been consumed, or return
                #  the event if something else should
                #  act upon it

                # Check first if the framework is going to
                #  consume this event. This includes heading
                #  into menus, possibly modifying the plugin
                #  model (previous/next plugin)
                #e = self.handle_event(e)
                #if e is None:
                #	continue

                # See if the menu wants to consume this event
                e = menu.handle_event(e)
                if e is None:
                    continue

                #self.print_input_event(e)

                # Next pass it on to the current plugin, if
                #  there is one
                if current_plugin is not None:
                    #					e = current_plugin['instance'].handle_event(e)
                    e = current_plugin.instance.handle_event(e)
                    if e is None:
                        continue

            # Ask the framework if it thinks it is displaying something
            # display_frame = self.draw_frame(canvas)
            # Ask the menu if it wants to draw something
            display_frame = menu.draw_frame(canvas)
            if display_frame is None:
                # If there is nothing to draw from the framework, ask
                #  the current plugin to do something is there is one
                if current_plugin is not None:
                    #					display_frame = current_plugin['instance'].draw_frame(canvas)
                    # There is every chance that the plugin might throw an
                    #  exception as we have no control over the quality of
                    #  external code (or some in-house code!), so catch any
                    #  exception
                    try:
                        display_frame = current_plugin.instance.draw_frame(
                            canvas)
                    except Exception as e:
                        self.logger.warn(
                            "Current plugin threw an error whilst running draw_frame()"
                        )
                        self.logger.warn(e)
                        display_frame = self.draw_error(canvas)

                else:
                    # If there is no plugin, then the framework should
                    #  do something
                    pass

            # Send the data to all configured outputs if there is any to send
            # TODO: If the plugin doesn't return a canvas for some reason,
            #        then the gui should be updated with a canvas, but the output
            #        plugins need not be informed. The problem is that if the plugin
            #        happens to never return anything, then the gui is never updated
            if display_frame is None:
                canvas.set_colour((0, 0, 0))
                display_frame = canvas

            for output_device in output_devices:
                output_device.send_data(display_frame)

            # Limit the framerate, we need not do it in the plugins - they really shouldn't
            #  mind that we are running at a max of 25fps
            self.clock.tick(25)

        pygame.quit()
        exit()