otwarchive-symphonyarchive/app/models/potential_matcher/potential_matcher_progress.rb
2026-03-11 22:22:11 +00:00

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