#!/usr/bin/ruby
#
# An object-oriented implementation of poll(2) for Ruby
#
# == Synopsis
#
# require 'poll'
# require 'socket'
#
# pollobj = Poll::new
#
# sock = TCPServer::new('localhost', 1138)
# pollobj.register( sock, Poll::RDNORM ) {|sock,evmask|
# case evmask
# when Poll::RDNORM
# clsock = sock.accept
# pollobj.mask( clsock, Poll::RDNORM, clientHandler )
#
# when Poll::HUP|Poll::ERR|Poll::NVAL
# pollobj.remove( io )
# $stderr.puts "Server error: Shutting down"
#
# else
# $stderr.puts "Unhandled event: #{evmask}"
# end
# }
#
# pollobj.poll( 0.25 ) until poll.handles.empty?
#
# == Author
#
# Michael Granger <ged@FaerieMUD.org>
#
# Copyright (c) 2002 The FaerieMUD Consortium. All rights reserved.
#
# This module is free software. You may use, modify, and/or redistribute this
# software under the same terms as Ruby itself.
#
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.
#
# == Version
#
# $Id: poll.rb,v 1.10 2002/10/21 03:47:01 deveiant Exp $
#
require 'delegate'
require 'poll.so'
### An object-oriented poll() implementation for Ruby
class Poll
### A Fixnum derivative that does bitwise AND for ===.
class EventMask < DelegateClass( Fixnum )
### Create and return a new Poll::EventMask object with the specified
### bitmask (an Integer).
def initialize( mask )
mask = mask.to_i
@mask = mask
super( mask )
end
### Returns true if the receiver bitwise ANDed with <tt>otherNum</tt> is
### non-zero. This is useful for using bitmasks in case blocks.
def ===( otherNum )
( self & otherNum ).nonzero?
end
### Returns a new EventMask after ORing the receiver with the specified
### value.
def |( otherNum )
otherNum = otherNum.to_i
return EventMask::new( @mask | otherNum )
end
### Returns a new EventMask after ANDing the receiver with the specified
### value.
def &( otherNum )
otherNum = otherNum.to_i
return EventMask::new( @mask & otherNum )
end
### Returns a new EventMask after XORing the receiver with the specified
### value.
def ^( otherNum )
otherNum = otherNum.to_i
return EventMask::new( @mask ^ otherNum )
end
end # class Poll::EventMask
### Class constants
Version = /([\d\.]+)/.match( %q$Revision: 1.10 $ )[1]
Rcsid = %q$Id: poll.rb,v 1.10 2002/10/21 03:47:01 deveiant Exp $
### Create and return new poll object.
def initialize
@masks = {}
@events = Hash::new( 0 )
@callbacks = {}
end
######
public
######
### Register the specified IO object with the specified
### <tt>eventMask</tt>. If the optional <tt>callback</tt> parameter (a
### Method or Proc object) or a <tt>block</tt> is given, it will be called
### with <tt>io</tt> and the mask of the event/s whenever #poll generates
### any events for <tt>io</tt>. If the <tt>callback</tt> parameter non-nil,
### any <tt>block</tt> specified is discarded. Any <tt>arguments</tt>
### specified are passed to the callback as the third and succeeding
### arguments. The following event masks can be set in the
### <tt>eventMask</tt>:
### [<tt>Poll::IN</tt>]
### Data other than high-priority data may be read without blocking.
### [<tt>Poll::PRI</tt>]
### High-priority data may be received without blocking.
### [<tt>Poll::OUT</tt>]
### Normal data (priority band equals 0) may be written without blocking.
###
### The following masks are ignored in the <tt>eventMask</tt>, as they are
### always implicitly set, but they may be specified in the handler
### <tt>callback</tt> or <tt>block</tt> to trap the conditions they
### represent:
### [<tt>Poll::ERR</tt>]
### An error has occurred on the device.
### [<tt>Poll::HUP</tt>]
### The device has been disconnected. This event and Poll::OUT are
### mutually exclusive; a device can never be writable once a hangup has
### occurred. However, this event and Poll::IN, Poll::RDNORM,
### Poll::RDBAND, or Poll::PRI are not mutually exclusive.
### [<tt>Poll::NVAL</tt>]
### The <tt>io</tt> object specified is invalid -- it has been closed, has
### a bad file descriptor, etc.
###
### If your operating system defines them, these masks are also available:
### [<tt>Poll::RDNORM</tt>]
### Normal data (priority band equals 0) may be read without blocking.
### [<tt>Poll::RDBAND</tt>]
### Data from a non-zero priority band may be read without blocking.
### [<tt>Poll::WRNORM</tt>]
### Same as Poll::OUT.
### [<tt>Poll::WRBAND</tt>]
### Priority data (priority band greater than 0) may be written.
def register( io, eventMask, callback=nil, *arguments, &block )
raise TypeError, "#{io.class.name} does not appear to be file-descriptor-based" unless
io.respond_to?( :fileno ) && io.fileno
# Clear any old events for this handle
@events.delete( io )
# Set the mask
@masks[ io ] = 0
setMask( io, eventMask )
# Set the callback
setCallback( io, (callback||block), *arguments )
end
alias :add :register
### Remove the specified <tt>io</tt> from the receiver's list of registered
### handles, if present. Returns the handle if it was registered, or
### <tt>nil</tt> if it was not.
def unregister( io )
@events.delete( io )
@callbacks.delete( io )
@masks.delete( io )
end
alias :remove :unregister
### Returns true if the specified <tt>io</tt> is registered with the poll
### object.
def registered?( io )
return @masks.has_key?( io )
end
### Clear all registered handles from the poll object. Returns the handles
### that were cleared.
def clear
rv = @masks.keys
@events.clear
@callbacks.clear
@masks.clear
return rv
end
### Get the EventMask for the specified <tt>io</tt>.
def mask( io )
raise ArgumentError, "Handle #{io.inspect} is not registered" unless
@masks.has_key?( io )
return @masks[ io ]
end
### Set the EventMask for the specified <tt>io</tt> to the given
### <tt>eventMask</tt>.
def setMask( io, eventMask )
raise ArgumentError, "Handle #{io.inspect} is not registered" unless
@masks.has_key?( io )
return @masks[ io ] = EventMask::new( eventMask.to_i )
end
### Add (bitwise OR) the specified <tt>eventMask</tt> to the mask for the
### specified <tt>io</tt>. Returns the new mask.
def addMask( io, eventMask )
raise ArgumentError, "Handle #{io.inspect} is not registered" unless
@masks.has_key?( io )
@masks[ io ] |= eventMask.to_i
end
### Remove (bitwise XOR) the specified <tt>eventMask</tt> from the mask for
### the specified <tt>io</tt>. Returns the new mask.
def removeMask( io, eventMask )
raise ArgumentError, "Handle #{io.inspect} is not registered" unless
@masks.has_key?( io )
@masks[ io ] ^= eventMask.to_i
end
### Returns <tt>true</tt> if the specified <tt>io</tt> has a callback
### associated with it.
def hasCallback?( io )
@callbacks.has_key?( io )
end
alias :has_callback? :hasCallback?
### Returns the per-handle callback associated with the specified
### <tt>io</tt>. If no callback exists for the given <tt>io</tt>,
### <tt>nil</tt> is returned.
def callback( io )
return nil unless @callbacks.has_key? io
return @callbacks[io][:callback]
end
### Returns the per-handle callback arguments associated with the specified
### <tt>io</tt> as an Array. If no callback exists for the given
### <tt>io</tt>, <tt>nil</tt> is returned.
def args( io )
return nil unless @callbacks.has_key? io
return @callbacks[io][:args]
end
### Reset the per-handle callback associated with the specified <tt>io</tt>
### to the specified <tt>callback</tt> (a Proc or Method object) or
### <tt>block</tt>, if given, or to nil if not specified. Any arguments
### specified past the second will be passed to the callback as its
### arguments. Returns the old callback.
def setCallback( io, callback=nil, *args, &block )
raise ArgumentError, "Handle #{io.inspect} is not registered" unless
@masks.has_key?( io )
rv = nil
if @callbacks.has_key?( io )
rv = @callbacks[ io ][:callback]
end
if callback || block
@callbacks[ io ] = { :callback => (callback || block), :args => args }
else
@callbacks.delete( io )
end
return rv
end
### Call the system-level poll function with the handles registered to the
### receiver. Any callbacks specified when the handles were registered are
### run for those handles with events. If a block is given, it will be
### invoked once for each handle which doesn't have an explicit handler. The
### <tt>timeout</tt> argument is the number of floating-point seconds to
### wait for an event before returning; negative timeout values will cause
### #poll to block until there is at least one event to report. This method
### returns the number of handles on which one or more events occurred.
def poll( timeout=-1 ) # :yields: io, eventMask
raise TypeError, "Timeout must be Numeric, not a #{timeout.type.name}" unless
timeout.kind_of? Numeric
timeout = timeout.to_f
@events.clear
unless @masks.empty?
@events = _poll( @masks.to_a, timeout*1000 )
# For each io that had an event happen, call any callback associated
# with it, or failing that, any provided block
@events.each {|io,evmask|
if @callbacks.has_key?( io )
args = @callbacks[ io ][ :args ]
@callbacks[ io ][ :callback ].call( io,
EventMask::new(evmask),
*args )
elsif block_given?
yield( io, EventMask::new(evmask) )
end
}
end
@events.default = EventMask::new( 0 )
return @events.length
end
### Fetch an Array of handles which had the events specified by
### <tt>eventMask</tt> happen to them in the last call to #poll. If
### <tt>eventMask</tt> is <tt>nil</tt>, an Array of all handles
### with pending events is returned.
def events( eventMask=nil )
if eventMask
eventMask = eventMask.to_i
@events.find_all {|io,evmask| (evmask & eventMask).nonzero? }.collect {|io,evmask| io}
else
@events.keys
end
end
### Fetch an Array of handles that are masked to receive the specified
### <tt>eventMask</tt>. If <tt>eventMask</tt> is nil, an Array of all
### registered handles is returned.
def handles( eventMask=nil )
if eventMask
eventMask = eventMask.to_i
@masks.find_all {|io,evmask| (evmask & eventMask).nonzero? }.collect {|io,evmask| io}
else
@masks.keys
end
end
### Return a human-readable string describing the poll object.
def inspect
"<Poll: handles: %s, %d pending events>" % [@masks.inspect, @events.length]
end
end # class Poll
syntax highlighted by Code2HTML, v. 0.9.1