Skip to content

Instantly share code, notes, and snippets.

@4poc
Last active December 23, 2015 07:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save 4poc/6598991 to your computer and use it in GitHub Desktop.
Save 4poc/6598991 to your computer and use it in GitHub Desktop.
Implements a fiber extension for rbot plugins, adds a wait_for() functionality.

Fiber Extension for rbot plugins

This extension can be included in Plugin classes, works only with ruby >= 1.9.3/2.0.0. Does currently not work with threads unfortunately :(

It does extend it with the following:

  • adds a :fiber => true option to map/map! command mappings, this envelops each call to the command action/method in a new Fiber block

  • adds a new wait_for method that can be called from commands (that are mapped with :fiber => true)

      wait_for(pattern, source)
    
      pattern: waits for a message that matches this string or regular expression
      source: considers only messages from this Irc::User or from all users in Irc::Channel
    
require 'fiber'
module FiberExtension
@@root_fiber = Fiber.current
def map(template, options)
super(template, options)
register_fiber(@handler.last) if options and options[:fibers]
end
def map!(template, options)
super(template, options)
register_fiber(@handler.last) if options and options[:fibers]
end
# hooks the action method and wraps the call in handle_fiber
def register_fiber(tmpl)
action = tmpl.options[:action]
self.class.class_eval do
alias_method 'old_'+action, action
define_method action do |m, params|
handle_fiber do
send('old_'+action, m, params)
end
end
end
end
# just hooks the call in a fiber instance
def handle_fiber(&hook)
fiber = Fiber.new do
hook.call
end
fiber.resume
end
# source is either: Irc::User or Irc::Channel
def wait_for(pattern, source)
raise 'tried to yield root fiber!' if Fiber.current == @@root_fiber
add_fiber_resume_condition pattern, source
Fiber.yield
end
# adds a fiber to the list, that is resumed when a source's message matches pattern
def add_fiber_resume_condition(pattern, source)
@fiber_resume_condition = @fiber_resume_condition || []
# first remove all existing pattern/source conditions
remove_fiber_resume_condition(pattern, source)
# adds the resume condition
@fiber_resume_condition << {fiber: Fiber.current, pattern: pattern, source: source}
end
# removes the resume condition/ and destroys the fiber
def remove_fiber_resume_condition(pattern, source)
@fiber_resume_condition.delete_if { |x| x[:pattern] == pattern and x[:source] == source }
end
# listens for _all_ messages in channel/query
def message(m, dummy=nil)
return if not @fiber_resume_condition or @fiber_resume_condition.empty?
# need to iterate over a duplicate, because this might add a new condition..
# its resuming the fiber and this might trigger another wait_for() that adds
# to this condition array.
@fiber_resume_condition.dup.each do |condition|
resume_fiber_if(m, condition)
end
end
# resumes the fiber if the conditions are met, if the
# source(channel/user) sends a message not matching the pattern,
# the fiber is destroyed.
def resume_fiber_if(m, condition)
source = condition[:source]
pattern = condition[:pattern]
fiber = condition[:fiber] # condition stores a reference to the fiber aswell
if fiber_source_condition_matches(m, source)
if m.message.match pattern
fiber.resume
else
remove_fiber_resume_condition(pattern, source)
end
end
end
# the source of conditions might be a channel or user instance
def fiber_source_condition_matches(m, source)
if source.instance_of? Irc::Channel and m.channel == source
true
elsif source.instance_of? Irc::User and m.source == source
true
end
end
end
# demonstrates the fiber extension
class FiberExample < Plugin
include FiberExtension
def help(plugin, topic='')
'demonstrates wait_for: numbers, letters, words'
end
def numbers(m, params)
8.times do |number|
m.reply 'Your number this time: %d, say next for another number.' % number
# pauses the execution of this method, and waits for the user
# (that issued the command) to say 'next'
wait_for('next', m.source)
end
m.reply 'no more numbers :('
end
# more of the same, just to show that conflicts are resolved
def letters(m, params)
('a'...'g').to_a.each do |letter|
m.reply 'Your letter this time: %s, say next for another letter.' % letter
wait_for('next', m.source)
end
m.reply 'no more letters :('
end
# this listens for 'more' that can be said by anyone in the same channel
def words(m, params)
%w{foo bar baz qux}.each do |word|
m.reply 'Your word this time: %s, say more for more words.' % word
wait_for('more', m.channel)
end
m.reply 'no more words :('
end
end
plugin = FiberExample.new
plugin.map 'numbers', :fibers => true
plugin.map 'letters', :fibers => true #, :threaded => true (does not work with threads yet)
plugin.map 'words', :fibers => true
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment