class Async::Reactor

def self.run(*args, &block)

def self.run(*args, &block)
	if current = Task.current?
		reactor = current.reactor
		
		reactor.async(*args, &block)
	else
		reactor = self.new
		
		begin
			reactor.run(*args, &block)
		ensure
			reactor.close
		end
		
		return reactor
	end
end

def async(*ios, &block)

def async(*ios, &block)
	task = Task.new(ios, self, &block)
	
	# I want to take a moment to explain the logic of this.
	# When calling an async block, we deterministically execute it until the
	# first blocking operation. We don't *have* to do this - we could schedule
	# it for later execution, but it's useful to:
	# - Fail at the point of call where possible.
	# - Execute determinstically where possible.
	# - Avoid overhead if no blocking operation is performed.
	task.run
	
	# Async.logger.debug "Initial execution of task #{fiber} complete (#{result} -> #{fiber.alive?})..."
	return task
end

def close

def close
	@children.each(&:stop)
	
	@selector.close
	@selector = nil
end

def closed?

def closed?
	@selector.nil?
end

def initialize(wrappers: IO)

def initialize(wrappers: IO)
	super(nil)
	
	@wrappers = wrappers
	
	@selector = NIO::Selector.new
	@timers = Timers::Group.new
	
	@stopped = true
end

def register(*args)

def register(*args)
	@selector.register(*args)
end

def run(*args, &block)

def run(*args, &block)
	raise RuntimeError, 'Reactor has been closed' if @selector.nil?
	
	@stopped = false
	
	# Allow the user to kick of the initial async tasks.
	async(*args, &block) if block_given?
	
	@timers.wait do |interval|
		# - nil: no timers
		# - -ve: timers expired already
		# -   0: timers ready to fire
		# - +ve: timers waiting to fire
		interval = 0 if interval && interval < 0
		
		Async.logger.debug "[#{self} Pre] Updating #{@children.count} children..."
		Async.logger.debug @children.collect{|child| [child.to_s, child.alive?]}.inspect
		# As timeouts may have been updated, and caused fibers to complete, we should check this.
		
		# If there is nothing to do, then finish:
		Async.logger.debug "[#{self}] @children.empty? = #{@children.empty?} && interval #{interval.inspect}"
		return if @children.empty? && interval.nil?
		
		Async.logger.debug "Selecting with #{@children.count} fibers interval = #{interval}..."
		if monitors = @selector.select(interval)
			monitors.each do |monitor|
				if task = monitor.value
					# Async.logger.debug "Resuming task #{task} due to IO..."
					task.resume
				end
			end
		end
	end until @stopped
	
	return self
ensure
	Async.logger.debug "[#{self} Ensure] Exiting run-loop (stopped: #{@stopped} exception: #{$!})..."
	Async.logger.debug @children.collect{|child| [child.to_s, child.alive?]}.inspect
	@stopped = true
end

def sleep(duration)

def sleep(duration)
	task = Fiber.current
	
	timer = self.after(duration) do
		if task.alive?
			task.resume
		end
	end
	
	result = Fiber.yield
	
	raise result if result.is_a? Exception
ensure
	timer.cancel if timer
end

def stop

def stop
	@stopped = true
end

def timeout(duration)

def timeout(duration)
	backtrace = caller
	task = Fiber.current
	
	timer = self.after(duration) do
		if task.alive?
			error = TimeoutError.new("execution expired")
			error.set_backtrace backtrace
			task.resume error
		end
	end
	
	yield
ensure
	timer.cancel if timer
end

def with(io, &block)

def with(io, &block)
	async do |task|
		task.with(io, &block)
	end
end

def wrap(io, task)

def wrap(io, task)
	@wrappers[io].new(io, task)
end