GithubHelp home page GithubHelp logo

mikbe / eventable Goto Github PK

View Code? Open in Web Editor NEW
126.0 3.0 4.0 212 KB

Eventable is an easy to use and understand event model; it's the simplest way to add fine-grain events to your classes.

License: MIT License

Shell 0.46% Ruby 99.54%

eventable's Introduction

#Eventable#

An incredibly simple way to add events to your classes.

##Description##

Provides an easy to use and understand event model. Other systems did way too much for my needs: I didn't need to monitor network IO ports, I didn't want a central loop that polled IO, I just wanted a simple way to add real, non-polled events to a class and to register other classes to listen for those events.

If you want a simple way to add events to your classes without a bunch of other unrelated IO stuff this is the solution for you. (If you want to monitor IO events check out EventMachine)

You might be saying, "What about Observable? Why not just use that?" The problem with observable is that it saves a reference to the observing object. If you drop and add a bunch of observers without removing them from the observed object you've got the potential for a huge memory leak (depending on the size of your listeners of course).

With Eventable you don't have to worry about memory leaks because Eventable only make a reference to the listening object when it needs to talk to it. When it's done the reference goes out of scope and can be garbage collected.

Eventable will even automatically remove registered listeners when they get garbage collected. You can set up a listener and not worry about removing your event hook yourself; it's done for you.

Eventable also allows for more fine-grain control than Observable. You can register for specific events instead of just saying, "Hey, let me know when anything changes."

##Concurrency considerations##

Events and threads do not scale well past a certain point but that's OK; they aren't meant to. They are meant for fast, simple communication beteen processes not large distributed processes like serving websites on massive server farms. If you need a solution that scales well to large distributed systems check out the Actor concurrency model.

##Install##

$ gem install eventable

##Usage Instructions##

  • Include the module
  • Important: If you have an initialize method super must be the first line of that method (see below). If you don't have an initialize method you don't have to add one. Super is called automatically for you.
  • Add an event, e.g. event :your_event
  • Fire the event when it should be fired: fire_event(:your_event)

To reiterate you must call super in your initialize method or Eventable won't work and you'll get an error. Eventable needs to create a mutex to make it thread safe, if you don't call super the mutex variable won't be created.

##Examples## This example shows the basics of using Eventable to add an event to a class and listen for that event. (Without threading it's a bit pointless):

require 'eventable'

class EventClass
  include Eventable

  # This is all you have to do to add an event (after you include Eventable)
  event :stuff_happens

  # There's no initialize method so you do
  # not have to worry about calling super.

  # don't name your method fire_event, that's taken
  def do_event
    puts "firing :stuff_happens"
    # And this is all you have to do to make the event happen
    fire_event(:stuff_happens, rand(1000))
  end

end

class ListenClass

  def stuff_happened(stuff)
    puts "stuff happened callback: #{stuff}"
  end

end

# Create an instance of a class that has an event
evented   = EventClass.new

# Create a class that listens for that event
listener  = ListenClass.new

# Register the listener with the instance that will have the event
evented.register_for_event(event: :stuff_happens, listener: listener, callback: :stuff_happened)

# We'll just arbitrarilly fire the event to see how it works
evented.do_event

# Wait just to be sure you see it happen
sleep(1)

=> firing :stuff_happens
=> stuff happened callback: 575

This example shows you how you might actually use it in a multi-threaded environment but is a lot harder to read because of all the debug code:

require 'eventable'

class EventedClass
  include Eventable
  event :stuff_happens
  event :other_stuff_happens

  def initialize
    # If you don't call super Eventable will raise an error
    super # <= VERY important, comment this out to see the error
    # do your initialize stuff
  end

  def make_stuff_happen(parent_id)
    # You handle concurrency however you want, threads or fibers, up to you.
    Thread.new{
      puts "firing :stuff_happens"
      fire_event(:stuff_happens, {:parent_id=>parent_id, :some_value => rand(1000)})
    }
  end

  def start_other_stuff_happening
    Thread.new {
      5.times do 
        sleep(rand(1)+2)
        puts "firing :other_stuff_happens"
        fire_event(:other_stuff_happens)
      end
    }
  end

end

class ListenerClass

  def initialize(some_object)
    @some_thing = some_object
    @some_thing.register_for_event(event: :stuff_happens, listener: self, callback: :stuff_happened)
  end

  def do_somestuff(parent_id, times=6)
    # I wrapped this in a thread to show it works cross threaded
    Thread.new{
      id = rand(1000)
      times.times do
        sleep(rand(2)+1)
        puts "[#{parent_id}, #{id}]: do_somestuff"
        @some_thing.make_stuff_happen(parent_id)
      end
    }
  end

  def stuff_happened(stuff)
    puts "[#{stuff[:parent_id]}] stuff_happened callback: #{stuff[:some_value]}"
  end

  def other_stuff_happened
    puts "[n/a] other_stuff_happened callback: n/a"
  end

end

# Now show it running
evented = EventedClass.new

# You can inject the evented class
listener = ListenerClass.new(evented)

# or attach to events outside of a listener class
evented.register_for_event(event: :other_stuff_happens, listener: listener, callback: :other_stuff_happened)

# Start firing off the events
evented.start_other_stuff_happening

(1..3).each do |index|
  listener.do_somestuff(index)
  puts "[#{index}] did some stuff, sleeping"
  sleep(rand(3)+4)
  puts "[#{index}] slept"
end

puts "all done"

##Version History##

2014.09.10 Ver: 0.2.1

  • Verified to work with Ruby 2.1
  • Updated specs to RSpec 2.99
  • Updated dependencies and author links.

2014.03.26 Ver: 0.2.0

Updates:

Updating for Ruby 2.x

2011.07.05 Ver: 0.1.4

Updates:

  • Added running specs to rake tasks (Colin Gemmell)

Bug fixes:

  • Did not accept initialization parameters so caused errors if your class inherited from another class that needed them. (Paul Strong)

2011.06.28
Ver: 0.1.3

Updates:

Callbacks are now threaded:
This patches one of the last concurrency issues; if a callback takes a long time or hangs it won't affect any other callbacks or events that need to fire.

It's your responsiblity to make sure your callback works, as long as it does the callback thread will go out of scope (unless you retain it) and everyone is happy.

2011.06.17
Ver: 0.1.2

Design updates/fixes:

  • Renamed most instance variables to help avoid name collisions.
  • Threadsafe mutex creation. Make sure you call super in your class's initialize method! (Robert Klemme)

2011.06.10
Ver: 0.1.1

Features:

  • If events fired specifically returns true and returns false if it can't for whatever reason (e.g. no listeners registered).

Fixes:

  • Throws error if event is fired and no listeners are registered (Benjamin Yu)
  • Workaround for RubyGems pre 1.8.3 date bug that locks up all of RubyGems (Benjamin Yu)

2011.06.06
Ver: 0.1.0
Went crazy and just completely wrapped all calls that modify or read callbacks cache with an instance mutex.

From what I understand locks are really expensive in Ruby so I'll need to clean this up and do some real performance testing.

Note: Releasing just to stop RubyGems.org from showing last beta instead of newest beta when there are only --pre versions available of a gem. I get why they do it, but it's annoying to have people downloading beta 1 when you're really on beta 2. Plus I need to start using it myself...

2011.06.05
Ver: 0.1.0.beta2

  • Wrapped #_id2ref call in begin...rescue block. (Evan Phoenix)
  • Added thread synchronization to calls that modify or read callbacks cache.

2011.06.05
Ver: 0.1.0.beta1
Completely redesigned from naive first attempt.

Added features

Now includes RSpecs.

Garbage collection safe:

  • Won't keep references to listeners open.
  • Automatically removes garbage collected listeners from event notifications.

2011.06.04
Ver: 0.0.1.alpha
Just wrote it as a proof of concept.

##Patches/Pull requests##

  • Fork the project.
  • Make your feature addition or bug fix (do not alter whitespace unless that is a bug!)
  • Add RSpecs for the fix/feature. If you don't have specs I can't add it.
  • Commit your changes. (do not change the rakefile, version, or history)
  • Send a pull request. I respond to pull request very, very quickly.

eventable's People

Contributors

mikbe avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

eventable's Issues

calling fire_event in an event class w/o first registering listeners raises and error.

$ irb
ruby-1.9.2-p180 :001 > require 'eventable'
 => true 
ruby-1.9.2-p180 :002 > 
ruby-1.9.2-p180 :003 >   class EventClass
ruby-1.9.2-p180 :004?>     include Eventable
ruby-1.9.2-p180 :005?>   
ruby-1.9.2-p180 :006 >       # This is all you have to do to add an event (after you include Eventable)
ruby-1.9.2-p180 :007 >       event :stuff_happens
ruby-1.9.2-p180 :008?>   
ruby-1.9.2-p180 :009 >       # don't name your method fire_event, that's taken
ruby-1.9.2-p180 :010 >       def do_event
ruby-1.9.2-p180 :011?>         puts "firing :stuff_happens"
ruby-1.9.2-p180 :012?>         # And this is all you have to do to make the event happen
ruby-1.9.2-p180 :013 >           fire_event(:stuff_happens, rand(1000))
ruby-1.9.2-p180 :014?>       end
ruby-1.9.2-p180 :015?>   
ruby-1.9.2-p180 :016 >     end
 => nil 
ruby-1.9.2-p180 :017 > 
ruby-1.9.2-p180 :018 >   # Create an instance of a class that has an event
ruby-1.9.2-p180 :019 >   evented   = EventClass.new
 => #<EventClass:0x1bad00> 
ruby-1.9.2-p180 :020 > # We'll just arbitrarilly fire the event to see how it works
ruby-1.9.2-p180 :021 >   evented.do_event
firing :stuff_happens
NoMethodError: undefined method `synchronize' for nil:NilClass
    from ~/.rvm/gems/ruby-1.9.2-p180@develop/gems/eventable-0.1.0/lib/eventable/eventable.rb:36:in `fire_event'
    from (irb):13:in `do_event'
    from (irb):21
    from ~/.rvm/rubies/ruby-1.9.2-p180/bin/irb:16:in `<main>'
ruby-1.9.2-p180 :022 > 

This is because line 36 of eventable.rb tries to synchronize on a mutex that doesn't exist. The mutex is created in the registration of a first event.

Possible fix is to guard the synchronize call with a nil check?

The next thing, I'm not sure about because I'm unfamiliar with underlying language implementation in the VMs. This is me mostly just pondering because of the likelihood of this happening seems very small.

Also, could there exist a possible problem (seems very unlikely, but theoretically possible) when multiple threads attempt to register listeners at the same time on an event class that has had no previous registered listeners. The call:

    @mutex ||= Mutex.new

I don't think the ||= operator is atomic??? Thus it possibly be that thread A sees that @mutex is nil so it will create a new mutex. But before the assignment, that thread is immediately interrupted by thread B who is also registering an event. It goes past this line, creating a new mutex. Then thread A runs again, thus completing the mutex assignment. They both run code in the synchronized block... And proceeds to do unpredictable things in the ||= type assignments in the protected code w.r.t the @callbacks.

Invalid gemspec in [...] invalid date format in specification: "2011-06-06 00:00:00.000000000Z"

$ ruby --version
ruby 1.9.2p180 (2011-02-18 revision 30909) [i386-darwin9.8.0]

$ gem --version
1.8.2

$ gem install eventable
Fetching: eventable-0.1.0.gem (100%)
Invalid gemspec in [/.rvm/gems/ruby-1.9.2-p180@develop/specifications/eventable-0.1.0.gemspec]: invalid date format in specification: "2011-06-06 00:00:00.000000000Z"
ERROR: While executing gem ... (NoMethodError)
undefined method `name' for nil:NilClass
[
]
$

Now this error'd gemspec essentially denies the ruby system from being able to boot.

Removing the time segment from the gemspec seems to fix the problem.

Event callbacks are blocking

Event callbacks are currently blocking so if one callback locks up it breaks all the following callbacks and the evented class itself locks up.

Callbacks should be threaded/fibered to avoid this.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.