Files
news.gitea.happylizard.me/bin/gems/io-event-1.9.0/lib/io/event/timers.rb
T
pizza2d1 4cee170d66
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 9s
Add bin and edit workflow
2026-09-16 13:11:16 -06:00

150 lines
3.6 KiB
Ruby

# frozen_string_literal: true
# Released under the MIT License.
# Copyright, 2024, by Samuel Williams.
require_relative "priority_heap"
class IO
module Event
# An efficient sorted set of timers.
class Timers
# A handle to a scheduled timer.
class Handle
# Initialize the handle with the given time and block.
#
# @parameter time [Float] The time at which the block should be called.
# @parameter block [Proc] The block to call.
def initialize(time, block)
@time = time
@block = block
end
# @attribute [Float] The time at which the block should be called.
attr :time
# @attribute [Proc | Nil] The block to call when the timer fires.
attr :block
# Compare the handle with another handle.
#
# @parameter other [Handle] The other handle to compare with.
# @returns [Boolean] Whether the handle is less than the other handle.
def < other
@time < other.time
end
# Compare the handle with another handle.
#
# @parameter other [Handle] The other handle to compare with.
# @returns [Boolean] Whether the handle is greater than the other handle.
def > other
@time > other.time
end
# Invoke the block.
def call(...)
@block.call(...)
end
# Cancel the timer.
def cancel!
@block = nil
end
# @returns [Boolean] Whether the timer has been cancelled.
def cancelled?
@block.nil?
end
end
# Initialize the timers.
def initialize
@heap = PriorityHeap.new
@scheduled = []
end
# @returns [Integer] The number of timers in the heap.
def size
flush!
return @heap.size
end
# Schedule a block to be called at a specific time in the future.
#
# @parameter time [Float] The time at which the block should be called, relative to {#now}.
# @parameter block [Proc] The block to call.
def schedule(time, block)
handle = Handle.new(time, block)
@scheduled << handle
return handle
end
# Schedule a block to be called after a specific time offset, relative to the current time as returned by {#now}.
#
# @parameter offset [#to_f] The time offset from the current time at which the block should be called.
# @yields {|now| ...} When the timer fires.
def after(offset, &block)
schedule(self.now + offset.to_f, block)
end
# Compute the time interval until the next timer fires.
#
# @parameter now [Float] The current time.
# @returns [Float | Nil] The time interval until the next timer fires, if any.
def wait_interval(now = self.now)
flush!
while handle = @heap.peek
if handle.cancelled?
@heap.pop
else
return handle.time - now
end
end
end
# @returns [Float] The current time.
def now
::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
end
# Fire all timers that are ready to fire.
#
# @parameter now [Float] The current time.
def fire(now = self.now)
# Flush scheduled timers into the heap:
flush!
# Get the earliest timer:
while handle = @heap.peek
if handle.cancelled?
@heap.pop
elsif handle.time <= now
# Remove the earliest timer from the heap:
@heap.pop
# Call the block:
handle.call(now)
else
break
end
end
end
# Flush all scheduled timers into the heap.
#
# This is a small optimization which assumes that most timers (timeouts) will be cancelled.
protected def flush!
while handle = @scheduled.pop
@heap.push(handle) unless handle.cancelled?
end
end
end
end
end