From 8cc6116cdff220215ce43479d72e0fd1565d025a Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Wed, 6 Apr 2022 12:16:58 -0700 Subject: [PATCH] Store previous successful lookups for future graph generation --- .../serve/mappers/internal/graph/mappers.rb | 96 ++++++++++++++++--- 1 file changed, 85 insertions(+), 11 deletions(-) diff --git a/plugins/commands/serve/mappers/internal/graph/mappers.rb b/plugins/commands/serve/mappers/internal/graph/mappers.rb index d0053771a..0b052d03c 100644 --- a/plugins/commands/serve/mappers/internal/graph/mappers.rb +++ b/plugins/commands/serve/mappers/internal/graph/mappers.rb @@ -9,10 +9,10 @@ module VagrantPlugins include Util::HasLogger # Weight given to root vertex ROOT_WEIGHT = 0 + # Weight given to source vertex or initial value + SOURCE_WEIGHT = 0 # Weight given to the destination vertex FINAL_WEIGHT = 0 - # Weight given to first input value vertex - BASE_WEIGHT = 0 # Weight given to output types matching final DST_WEIGHT = 0 # Weight given to named input value vertices @@ -26,6 +26,8 @@ module VagrantPlugins # @return [Graph] graph instance representing mappers attr_reader :graph + # @return [Object] source input value + attr_reader :source # @return [Array] input values attr_reader :inputs # @return [String] named input to prefer @@ -34,6 +36,52 @@ module VagrantPlugins attr_reader :mappers # @return [Class] expected return type attr_reader :final + # @return [Boolean] graph is fresh (not using cached vertex list) + attr_reader :fresh + # @return [Array] list of callables used for Method vertices + attr_reader :callables + + @previous = {} + + class << self + # Register a valid path for a given source + # and destination + # + # @param src [Class] source type + # @param dst [Class] destination type + # @param path [Array] + def register(src, dst, path) + begin + @previous[generate_key(src, dst)] = path + rescue KeyError + nil + end + end + + # Fetch a path for a given source and destination + # if it has been registered + # + # @param src [Class] source type + # @param dst [Class] destination type + # @return [Array, NilClass] + def previous(src, dst) + begin + @previous[generate_key(src, dst)] + rescue KeyError + nil + end + end + + # Generate lookup key for given source + # and destination + # + # @param src [Class] source + # @param dst [Class] destination + # @return [String] + def generate_key(src, dst) + "#{src} -> #{dst}" + end + end # Wrap a mappers instance into a graph with input values # and determine and execute required path for desired output @@ -42,24 +90,41 @@ module VagrantPlugins # @param input_values [Array] Values provided for execution # @param mappers [Mappers] Mappers instance to use # @param named [String] Named input to prefer - def initialize(output_type:, input_values:, mappers:, named: nil) + # @param source [Object] Source value for conversion (optional) + def initialize(output_type:, input_values:, mappers:, named: nil, source: nil) if !output_type.nil? && !output_type.is_a?(Class) && !output_type.is_a?(Module) raise TypeError, "Expected output type to be `Class', got `#{output_type.class}' (#{output_type})" end @final = output_type + @source = source @inputs = Array(input_values) if !mappers.is_a?(CommandServe::Mappers) raise TypeError, "Expected mapper to be `Mappers', got `#{mappers.class}'" end @mappers = mappers + @callables = mappers.mappers @named = named.to_s + @fresh = true + + # If we have a valid source type (and are not generating a value) check + # if a path has already been registered for this source/destination + # pair. If it has, we can fetch the callables out of the registered + # path and build our graph only using that subset of callables + if source != GENERATE_CLASS && self.class.previous(source, final) + @callables = self.class.previous(source, final).find_all { |v| + v.is_a?(Vertex::Method) + }.map(&:callable) + # Since we built the graph using a registered path lookup, mark + # this as non-fresh + @fresh = false + end setup! - logger.debug("new graph mappers instance created #{self}") - logger.trace("graph: #{graph.inspect}") + logger.debug { "new graph mappers instance created #{self}" } + logger.trace { "graph: #{graph.inspect}" } end # Generate path and execute required mappers @@ -69,7 +134,7 @@ module VagrantPlugins # Generate list of vertices to reach destination # from root, if possible search = Search.new(graph: graph) - logger.debug("searching for conversion path #{inputs.first} -> #{final}") + logger.debug { "searching for conversion path #{source ? source.class : inputs.first.class} -> #{final}" } p = search.path(@root, @dst) logger.debug { @@ -79,6 +144,7 @@ module VagrantPlugins }.join(" ->\n ") "found execution path:\n #{sp}" } + # Call root first and validate it was # actually root. The value is a stub, # so it's not saved. @@ -87,6 +153,12 @@ module VagrantPlugins raise "Initial vertex is not root. Expected `:root', got `#{result}'" end + # If we were not generating a value and the graph was fresh, + # register this path lookup for future use. + if source != GENERATE_CLASS && fresh + self.class.register(source, final, p) + end + # Execute each vertex in the path p.each do |v| # The argument list required by the current @@ -117,8 +189,10 @@ module VagrantPlugins @root = graph.add_vertex(Graph::Vertex::Root.new(value: :root)) @root.weight = ROOT_WEIGHT # Add the provided input values - root_value = true - input_vertices = inputs.map do |input_value| + input_vertices = [] + initial_value = true + + input_vertices += inputs.map do |input_value| next if input_value == GENERATE if input_value.is_a?(Type::NamedArgument) iv = graph.add_vertex( @@ -130,9 +204,9 @@ module VagrantPlugins iv.weight = input_value.name.to_s == named ? NAMED_VALUE_WEIGHT : VALUE_WEIGHT else iv = graph.add_vertex(Graph::Vertex::Value.new(value: input_value)) - iv.weight = root_value ? BASE_WEIGHT : VALUE_WEIGHT + iv.weight = initial_value ? SOURCE_WEIGHT : VALUE_WEIGHT end - root_value = false + initial_value = false graph.add_edge(@root, iv) iv end.compact @@ -147,7 +221,7 @@ module VagrantPlugins fn_outputs = Array.new # Create vertices for all our registered mappers, # as well as their inputs and outputs - mappers.mappers.each do |mapper| + callables.each do |mapper| fn = graph.add_vertex(Graph::Vertex::Method.new(callable: mapper)) fn_inputs += mapper.inputs.map do |i| iv = graph.add_vertex(Graph::Vertex::Input.new(type: i.type))