lib/qfill/manager.rb in qfill-0.0.4 vs lib/qfill/manager.rb in qfill-0.1.0

- old
+ new

@@ -1,166 +1,120 @@ -#Qfill::Manager.new( +# frozen_string_literal: true + +require 'forwardable' + +# A Qfill::Manager builds a set of result data (as `result`) from the source data in Qfill::Popper, +# according to the Qfill::Result definitions in the Qfill::Pusher, and the selected strategy. +# +# Qfill::Manager.new( # :all_list_max => 40, # :popper => popper, # :pusher => pusher, -#) +# ) module Qfill class Manager - attr_accessor :all_list_max, :popper, :pusher, :fill_count, :strategy, :result + extend Forwardable + def_delegators :@strategy, + :popper, + :pusher, + :result, + :remaining_to_fill + attr_accessor :all_list_max, + :primary_list_total, + :popper, + :pusher, + :fill_count, + :result, + :strategy_options - STRATEGY_OPTIONS = [:drain_to_limit, :drain_to_empty, :sample] + STRATEGY_OPTIONS = %i[drain_to_limit drain_to_empty sample time_slice].freeze def initialize(options = {}) - unless options[:popper] && options[:pusher] - raise ArgumentError, "#{self.class}: popper and pusher are required options for #{self.class}.new(options)" - end unless options[:strategy].nil? || STRATEGY_OPTIONS.include?(options[:strategy]) - if options[:strategy] == :drain - warn "Qfill strategy :drain has been renamed :drain_to_limit, please update your code." - options[:strategy] = :drain_to_limit - else - raise ArgumentError, "#{self.class}: strategy is optional, but must be one of #{STRATEGY_OPTIONS.inspect} if provided" - end + raise ArgumentError, + "#{self.class}: strategy is optional, but must be one of #{STRATEGY_OPTIONS.inspect} if provided" end + + @fill_count = 0 + @popper = options[:popper] @pusher = options[:pusher] - # Provided by user, or defaults to the total number of primary elements in popper list set - @all_list_max = options[:all_list_max] ? [options[:all_list_max], self.popper.count_primary_elements].min : self.popper.count_primary_elements - @fill_count = 0 - @strategy = options[:strategy] || :drain_to_limit # or :drain_to_empty or :sample + @strategy_name = options[:strategy] || :drain_to_limit # or :drain_to_empty or :sample + @strategy_options = options[:strategy_options] + + # Allow the strategy to define the pusher when not defined by user + @pusher ||= strategy.default_pusher + unless @popper && @pusher + raise ArgumentError, "#{self.class}: popper and pusher (except where defined by the strategy) are required options for #{self.class}.new(options)" + end + + # Provided by user, or defaults to the total number of elements in popper list set + @all_list_max = if options[:all_list_max] + [options[:all_list_max], + popper.count_all_elements].min + else + popper.count_all_elements + end + @primary_list_total = popper.count_primary_elements end + def strategy + @strategy ||= case @strategy_name + when :drain_to_empty + Qfill::Strategy::DrainToEmpty.new(self) + when :drain_to_limit + Qfill::Strategy::DrainToLimit.new(self) + when :sample + Qfill::Strategy::Sample.new(self) + when :time_slice + Qfill::Strategy::TimeSlice.new(self) + end + end + def fill! - while !is_full? && !self.popper.primary_empty? && (self.result = self.pusher.current_list) - if self.current_strategy == :drain_to_empty - preferred_potential_ratio = 0 - preferred_potential = 0 - self.result.list_ratios.each do |list_name, list_ratio| - poppy = self.result.preferred.select {|x| x == list_name} - if poppy - preferred_potential_ratio += list_ratio - num = self.popper[list_name].elements.length - preferred_potential += num - self.result.max_tracker[list_name] = num - end - end - self.result.preferred_potential = preferred_potential - self.result.preferred_potential_ratio = preferred_potential_ratio - end - self.fill_to_ratio! - self.pusher.set_next_as_current! - self.result.elements.shuffle! if self.result.shuffle + while !is_full? && !popper.primary_empty? && (self.result = pusher.current_list) + strategy.on_fill! + fill_to_ratio! + pusher.set_next_as_current! + result.elements.shuffle! if result.shuffle end end def fill_to_ratio! - case self.current_strategy - when :drain_to_empty then - result.max = self.result.preferred_potential_ratio > 0 ? [(self.result.preferred_potential / self.result.preferred_potential_ratio), self.remaining_to_fill].min : self.remaining_to_fill - when :drain_to_limit, :sample then - result.max = Qfill::Result.get_limit_from_max_and_ratio(self.remaining_to_fill, result.ratio) - end - #result.max = Qfill::Result.get_limit_from_max_and_ratio(self.all_list_max, result.ratio) - if !result.list_ratios.empty? - self.fill_according_to_list_ratios! + strategy.result_max! + if result.list_ratios.empty? + fill_up_to_ratio! else - self.fill_up_to_ratio! + fill_according_to_list_ratios! end end def remaining_to_fill - self.all_list_max - self.fill_count + primary_list_total - fill_count end # Go through the queues this result should be filled from and push elements from them onto the current result list. def fill_according_to_list_ratios! - added = 0 - tally = 0 - ratio_modifier = 1 - case self.current_strategy - when :drain_to_empty then - # Are there any elements in preferred queues that we should add? - if self.result.preferred_potential > 0 - # Setup a ratio modifier for the non-preferred queues - result.list_ratios.each do |list_name, list_ratio| - max_from_list = self.result.max_tracker[list_name] || Qfill::Result.get_limit_from_max_and_ratio(result.max, list_ratio) - array_to_push = self.popper.next_objects!(list_name, max_from_list) - self.popper.current_index = self.popper.index_of(list_name) - added = result.push(array_to_push, list_name) - puts "[fill_according_to_list_ratios!]#{self}[#{list_name}][added:#{added}]" if Qfill::VERBOSE - tally += added - end - self.fill_count += tally - end - when :drain_to_limit - result.list_ratios.each do |list_name, list_ratio| - max_from_list = Qfill::Result.get_limit_from_max_and_ratio(result.max, list_ratio) - array_to_push = self.popper.next_objects!(list_name, max_from_list) - self.popper.current_index = self.popper.index_of(list_name) - added = result.push(array_to_push, list_name) - puts "[fill_according_to_list_ratios!]#{self}[#{list_name}][added:#{added}]" if Qfill::VERBOSE - tally += added - end - self.fill_count += tally - when :sample then - #puts "#{!is_full?} && #{result.fill_count} >= #{result.max} && #{!self.popper.totally_empty?} && #{(list_ratio_tuple = result.current_list_ratio)}" - while !is_full? && !result.is_full? && !self.popper.totally_empty? && (list_ratio_tuple = result.current_list_ratio) - max_from_list = Qfill::Result.get_limit_from_max_and_ratio(result.max, list_ratio_tuple[1]) - array_to_push = self.popper.next_objects!(list_ratio_tuple[0], max_from_list) - added = result.push(array_to_push, list_ratio_tuple[0]) - self.fill_count += added - puts "[fill_according_to_list_ratios!]#{self}[#{list_ratio_tuple[0]}][added:#{added}]" if Qfill::VERBOSE - result.set_next_as_current! - end - end + strategy.fill_according_to_list_ratios! end # Go through the primary (non backfill) queues in the popper and push elements from them onto the current result list. def fill_up_to_ratio! - added = 0 - tally = 0 - if self.current_strategy == :drain_to_empty - self.popper.primary.each do |queue| - array_to_push = self.popper.next_objects!(queue.name, result.max) - added = result.push(array_to_push, queue.name) - self.popper.current_index = self.popper.index_of(queue.name) - puts "[fill_up_to_ratio!]#{self}[Q:#{queue.name}][added:#{added}]" if Qfill::VERBOSE - tally += added - end - self.fill_count += added - else - ratio = 1.0 / self.popper.primary.length # 1 divided by the number of queues - max_from_list = Qfill::Result.get_limit_from_max_and_ratio(result.max, ratio) - if self.current_strategy == :drain_to_limit - self.popper.primary.each do |queue| - array_to_push = self.popper.next_objects!(queue.name, max_from_list) - added = result.push(array_to_push, queue.name) - self.popper.current_index = self.popper.index_of(queue.name) - puts "[fill_up_to_ratio!]#{self}[Q:#{queue.name}][added:#{added}]" if Qfill::VERBOSE - tally += added - end - self.fill_count += tally - elsif self.current_strategy == :sample - while !is_full? && !result.is_full? && !self.popper.totally_empty? && (origin_list = self.popper.current_list) - array_to_push = self.popper.next_objects!(origin_list.name, max_from_list) - added = result.push(array_to_push, origin_list.name) - self.fill_count += added - puts "[fill_up_to_ratio!]#{self}[Added:#{added}][Max List:#{max_from_list}][ratio:#{ratio}][added:#{added}]" if Qfill::VERBOSE - self.popper.set_next_as_current! - end - end - end + strategy.fill_up_to_ratio! end - def current_strategy - (result.strategy || self.strategy) + def is_full? + fill_count >= all_list_max end - def is_full? - self.fill_count >= self.all_list_max + def each(&block) + # NOTE: on magic: http://blog.arkency.com/2014/01/ruby-to-enum-for-enumerator/ + return enum_for(:each) unless block # Sparkling magic! + + pusher.each(&block) end def to_s - "[#{self.current_strategy}][Result Max:#{result.max}][All Max:#{self.all_list_max}][Current Max:#{self.result.max}][Filled:#{self.fill_count}][Primary #:#{self.popper.count_primary_elements}]" + "[#{strategy_name}][Result Max:#{result.max}][All Max:#{all_list_max}][Current Max:#{result.max}][Filled:#{fill_count}][Primary #:#{popper.count_primary_elements}]" end end end