87 lines
3.4 KiB
Ruby
87 lines
3.4 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
# A class for recording the progress of potential match generation.
|
|
#
|
|
# Designed to make it easier to record progress on subtasks. It keeps two
|
|
# stacks: @progress, and @limit. When you call start_subtask, it pushes 0 onto
|
|
# the @progress stack, and the number of ticks in the subtask onto the @limit
|
|
# stack. When you call increment, it increases the value at the very end of the
|
|
# @progress stack (so that the percent completion for that subtask, computed
|
|
# with @progress.last / @limit.last, increases). When you call end_subtask, it
|
|
# pops the last value off of the @progress and @limit, so that you go back to
|
|
# recording progress for the previous subtask.
|
|
#
|
|
# The overall percent completion is computed by examining all subtasks on the
|
|
# stack. You start from the end of the stack (with the subtask that was added
|
|
# last), and divide the progress by the limit to get the fractional completion.
|
|
# Then you move back to the previous subtask, add that fractional completion to
|
|
# that subtask's progress, and divide by the limit. Repeat until you've reached
|
|
# the first subtask.
|
|
#
|
|
# For a concrete example: If your first subtask has a limit of 10 "ticks" of
|
|
# work, and you've called increment 3 times, the percent progress will be 30%.
|
|
# If you then create a subtask with a limit of 5 ticks, and you call increment
|
|
# once, then you're 1 / 5 = 0.2 = 20% of the way through the subtask, but your
|
|
# progress on the whole work is (3 + 0.2) / 10 = 32%. Another call to increment
|
|
# will bring you up to 34%, then 36%, 38%, and finally 40% when you finish all
|
|
# five ticks of work. Then you can call end_task and increment (which will keep
|
|
# you at 40%, but without the subtask), and continue with the work that will
|
|
# bring you from 40% to 50%.
|
|
class PotentialMatcherProgress
|
|
attr_reader :redis_progress_key
|
|
attr_reader :progress_enabled
|
|
|
|
# Create a new progress object.
|
|
def initialize(collection, progress_enabled = true)
|
|
@progress = []
|
|
@limit = []
|
|
@percent = 0
|
|
|
|
@progress_enabled = progress_enabled
|
|
@redis_progress_key = PotentialMatch.progress_key(collection)
|
|
end
|
|
|
|
# Signal that you're starting work on a subtask. The sole argument gives the
|
|
# number of "ticks" in the subtask that you're about to work on, so that it's
|
|
# possible to calculate the percent completion for the subtask.
|
|
def start_subtask(max)
|
|
@progress.push(0)
|
|
@limit.push(max)
|
|
end
|
|
|
|
# Signal that you're finishing work on the current subtask.
|
|
#
|
|
# NOTE: Never call start_subtask immediately after end_subtask. You should
|
|
# always have a call to increment in between, otherwise it'll look like your
|
|
# progress is getting lower. (In fact, in order to keep the counter from
|
|
# going backwards, you should always call increment immediately after
|
|
# end_subtask.)
|
|
def end_subtask
|
|
@progress.pop
|
|
@limit.pop
|
|
end
|
|
|
|
# Signal that you've just done a "tick" of work on the current subtask.
|
|
def increment
|
|
value = @progress.pop
|
|
@progress.push(value + 1)
|
|
update if @progress_enabled
|
|
end
|
|
|
|
# Calculate the percent progress.
|
|
def percent
|
|
value = 0.0
|
|
|
|
(@progress.size - 1).downto 0 do |index|
|
|
value = (@progress[index] + value) / @limit[index]
|
|
end
|
|
|
|
(value * 10_000).floor / 100.0
|
|
end
|
|
|
|
# Update the REDIS progress key with the current percent.
|
|
def update
|
|
return unless @progress_enabled
|
|
REDIS_GENERAL.set @redis_progress_key, percent.to_s
|
|
end
|
|
end
|