This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user