tribe 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.rdoc +2 -2
- data/Rakefile +35 -0
- data/VERSION +1 -1
- data/lib/tribe.rb +7 -0
- data/lib/tribe/actor.rb +20 -6
- data/lib/tribe/clock.rb +78 -0
- data/lib/tribe/registry.rb +23 -7
- data/lib/tribe/scheduler.rb +4 -4
- data/lib/tribe/timer.rb +51 -0
- data/spec/lib/tribe/clock_spec.rb +4 -0
- data/spec/lib/tribe/timer_spec.rb +4 -0
- data/tribe.gemspec +91 -0
- metadata +7 -2
data/README.rdoc
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
= tribe
|
2
2
|
|
3
|
-
Actor Model for Ruby.
|
3
|
+
Actor Model for Ruby. Currently experimental and not recommend for production. Many standard features of the Actor Model are missing.
|
4
4
|
|
5
5
|
== Contributing to tribe
|
6
|
-
|
6
|
+
|
7
7
|
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
|
8
8
|
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
|
9
9
|
* Fork the project.
|
data/Rakefile
CHANGED
@@ -54,3 +54,38 @@ rescue LoadError
|
|
54
54
|
end
|
55
55
|
|
56
56
|
require 'tribe'
|
57
|
+
|
58
|
+
class MyActor < Tribe::Actor
|
59
|
+
def pre_init
|
60
|
+
@count = 0
|
61
|
+
end
|
62
|
+
|
63
|
+
def increment
|
64
|
+
@count += 1
|
65
|
+
puts "#{@name}=#{@count}" if @count >= 50000
|
66
|
+
end
|
67
|
+
|
68
|
+
def go(friend_name)
|
69
|
+
friend = Tribe.registry[friend_name]
|
70
|
+
|
71
|
+
50000.times do
|
72
|
+
friend.increment!
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def tribe_demo
|
78
|
+
a1 = Tribe.registry['a1'] || MyActor.new(:name => 'a1')
|
79
|
+
a2 = Tribe.registry['a2'] || MyActor.new(:name => 'a2')
|
80
|
+
a3 = Tribe.registry['a3'] || MyActor.new(:name => 'a3')
|
81
|
+
a4 = Tribe.registry['a4'] || MyActor.new(:name => 'a4')
|
82
|
+
a5 = Tribe.registry['a5'] || MyActor.new(:name => 'a5')
|
83
|
+
a6 = Tribe.registry['a6'] || MyActor.new(:name => 'a6')
|
84
|
+
|
85
|
+
a1.go!('a2')
|
86
|
+
a2.go!('a1')
|
87
|
+
a3.go!('a4')
|
88
|
+
a4.go!('a3')
|
89
|
+
a5.go!('a6')
|
90
|
+
a6.go!('a5')
|
91
|
+
end
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.2
|
data/lib/tribe.rb
CHANGED
@@ -1,11 +1,14 @@
|
|
1
1
|
require 'singleton'
|
2
2
|
require 'thread'
|
3
|
+
require 'set'
|
3
4
|
|
4
5
|
require 'tribe/actor'
|
5
6
|
require 'tribe/singleton'
|
6
7
|
require 'tribe/worker'
|
7
8
|
require 'tribe/scheduler'
|
8
9
|
require 'tribe/registry'
|
10
|
+
require 'tribe/clock'
|
11
|
+
require 'tribe/timer'
|
9
12
|
|
10
13
|
module Tribe
|
11
14
|
def self.scheduler
|
@@ -15,4 +18,8 @@ module Tribe
|
|
15
18
|
def self.registry
|
16
19
|
Tribe::Registry.instance
|
17
20
|
end
|
21
|
+
|
22
|
+
def self.clock
|
23
|
+
Tribe::Clock.instance
|
24
|
+
end
|
18
25
|
end
|
data/lib/tribe/actor.rb
CHANGED
@@ -3,10 +3,15 @@ module Tribe
|
|
3
3
|
attr_reader :name
|
4
4
|
|
5
5
|
def initialize(options = {})
|
6
|
+
run_hook(:pre_init)
|
7
|
+
|
8
|
+
@alive = true
|
6
9
|
@process_lock = Mutex.new
|
7
10
|
@name = options[:name].freeze
|
8
11
|
|
9
12
|
Tribe.registry.register(self)
|
13
|
+
|
14
|
+
run_hook(:post_init)
|
10
15
|
end
|
11
16
|
|
12
17
|
def method_missing(method, *args, &block)
|
@@ -14,14 +19,14 @@ module Tribe
|
|
14
19
|
bang = m[-1] == '!'
|
15
20
|
|
16
21
|
if bang && respond_to?(m.chop!)
|
17
|
-
|
22
|
+
tell(m, *args)
|
18
23
|
else
|
19
24
|
super
|
20
25
|
end
|
21
26
|
end
|
22
27
|
|
23
|
-
def
|
24
|
-
Tribe.scheduler.schedule do
|
28
|
+
def tell(method, *args)
|
29
|
+
Tribe.scheduler.send(:schedule) do
|
25
30
|
process(:method => method, :args => args)
|
26
31
|
end
|
27
32
|
|
@@ -31,12 +36,21 @@ module Tribe
|
|
31
36
|
private
|
32
37
|
def process(message)
|
33
38
|
@process_lock.synchronize do
|
34
|
-
|
39
|
+
begin
|
40
|
+
send(message[:method], *message[:args]) if @alive
|
41
|
+
rescue Exception => e
|
42
|
+
@alive = false
|
43
|
+
end
|
35
44
|
end
|
36
45
|
end
|
37
46
|
|
38
|
-
def
|
39
|
-
|
47
|
+
def terminate
|
48
|
+
@alive = false
|
49
|
+
Tribe.registry.unregister(self)
|
50
|
+
end
|
51
|
+
|
52
|
+
def run_hook(hook)
|
53
|
+
send(hook) if respond_to?(hook, true)
|
40
54
|
end
|
41
55
|
end
|
42
56
|
end
|
data/lib/tribe/clock.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
module Tribe
|
2
|
+
class Clock < Tribe::Singleton
|
3
|
+
FREQUENCY = 100 # Hz.
|
4
|
+
|
5
|
+
def initialize(options = {})
|
6
|
+
@frequency = options[:frequency] || FREQUENCY
|
7
|
+
@run = true
|
8
|
+
@timers = SortedSet.new
|
9
|
+
@messages = Queue.new
|
10
|
+
@thread = Thread.new { main }
|
11
|
+
end
|
12
|
+
|
13
|
+
def shutdown
|
14
|
+
message = { :command => :shutdown }
|
15
|
+
@messages.push(message)
|
16
|
+
|
17
|
+
@thread.join
|
18
|
+
end
|
19
|
+
|
20
|
+
def schedule(timer)
|
21
|
+
message = { :command => :schedule, :timer => timer }
|
22
|
+
@messages.push(message)
|
23
|
+
|
24
|
+
true
|
25
|
+
end
|
26
|
+
|
27
|
+
def unschedule(timer)
|
28
|
+
message = { :command => :unschedule, :timer => timer }
|
29
|
+
@messages.push(message)
|
30
|
+
|
31
|
+
true
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
def main
|
36
|
+
sleep_val = 1.0 / @frequency
|
37
|
+
|
38
|
+
while @run
|
39
|
+
sleep(sleep_val)
|
40
|
+
process_commands
|
41
|
+
fire_timers
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def process_commands
|
46
|
+
@messages.length.times do
|
47
|
+
message = @messages.pop
|
48
|
+
|
49
|
+
case message[:command]
|
50
|
+
when :schedule
|
51
|
+
@timers.add(message[:timer])
|
52
|
+
when :unschedule
|
53
|
+
@timers.delete(message[:timer])
|
54
|
+
when :shutdown
|
55
|
+
@run = false
|
56
|
+
else
|
57
|
+
raise("Invalid command: #{message[:command]}")
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def fire_timers
|
63
|
+
count = 0
|
64
|
+
|
65
|
+
while true
|
66
|
+
return 0 if @timers.empty?
|
67
|
+
|
68
|
+
if (timer = @timers.first).send(:fire?)
|
69
|
+
@timers.delete(timer)
|
70
|
+
timer.send(:fire)
|
71
|
+
count += 1
|
72
|
+
else
|
73
|
+
return count
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
data/lib/tribe/registry.rb
CHANGED
@@ -2,28 +2,44 @@ module Tribe
|
|
2
2
|
class Registry < Tribe::Singleton
|
3
3
|
|
4
4
|
def initialize
|
5
|
+
@lock = Mutex.new
|
6
|
+
|
5
7
|
@actors_by_oid = {}
|
6
8
|
@actors_by_name = {}
|
7
9
|
end
|
8
10
|
|
9
11
|
def register(actor)
|
10
|
-
@
|
12
|
+
@lock.synchronize do
|
13
|
+
@actors_by_oid[actor.object_id] = actor
|
11
14
|
|
12
|
-
|
13
|
-
|
15
|
+
if actor.name
|
16
|
+
if @actors_by_name[actor.name]
|
17
|
+
raise "Actor already exists. name=#{actor.name}"
|
18
|
+
else
|
19
|
+
@actors_by_name[actor.name] = actor
|
20
|
+
end
|
21
|
+
end
|
14
22
|
end
|
23
|
+
|
24
|
+
true
|
15
25
|
end
|
16
26
|
|
17
27
|
def unregister(actor)
|
18
|
-
@
|
28
|
+
@lock.synchronize do
|
29
|
+
@actors_by_oid.delete(actor.object_id)
|
19
30
|
|
20
|
-
|
21
|
-
|
31
|
+
if actor.name
|
32
|
+
@actors_by_name.delete(actor.name)
|
33
|
+
end
|
22
34
|
end
|
35
|
+
|
36
|
+
true
|
23
37
|
end
|
24
38
|
|
25
39
|
def [](val)
|
26
|
-
@
|
40
|
+
@lock.synchronize do
|
41
|
+
return @actors_by_name[val]
|
42
|
+
end
|
27
43
|
end
|
28
44
|
end
|
29
45
|
end
|
data/lib/tribe/scheduler.rb
CHANGED
@@ -8,10 +8,6 @@ module Tribe
|
|
8
8
|
spawn(@count)
|
9
9
|
end
|
10
10
|
|
11
|
-
def schedule(&block)
|
12
|
-
@messages.push({ :command => :perform, :task => block })
|
13
|
-
end
|
14
|
-
|
15
11
|
def shutdown
|
16
12
|
@count.times do
|
17
13
|
@messages.push({ :command => :shutdown })
|
@@ -23,6 +19,10 @@ module Tribe
|
|
23
19
|
end
|
24
20
|
|
25
21
|
private
|
22
|
+
def schedule(&block)
|
23
|
+
@messages.push({ :command => :perform, :task => block })
|
24
|
+
end
|
25
|
+
|
26
26
|
def spawn(count)
|
27
27
|
count.times do
|
28
28
|
worker = Worker.new(@messages)
|
data/lib/tribe/timer.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
module Tribe
|
2
|
+
class Timer
|
3
|
+
attr_reader :fire_at
|
4
|
+
attr_reader :repeat
|
5
|
+
|
6
|
+
def initialize(seconds, options = {}, &block)
|
7
|
+
@seconds = seconds.to_f
|
8
|
+
@callback = block
|
9
|
+
@clock = options[:clock] || Tribe.clock
|
10
|
+
@repeat = options[:repeat] || false
|
11
|
+
|
12
|
+
schedule
|
13
|
+
end
|
14
|
+
|
15
|
+
def cancel
|
16
|
+
@clock.unschedule(self)
|
17
|
+
end
|
18
|
+
|
19
|
+
def <=>(timer)
|
20
|
+
if self.object_id == timer.object_id
|
21
|
+
return 0
|
22
|
+
else
|
23
|
+
return self.fire_at <=> timer.fire_at
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
def fire?(current_time = nil)
|
29
|
+
current_time ||= now
|
30
|
+
|
31
|
+
now >= @fire_at
|
32
|
+
end
|
33
|
+
|
34
|
+
def fire
|
35
|
+
@callback.call
|
36
|
+
rescue Exception => e
|
37
|
+
puts "Timer caught exception: #{e.message}\n#{e.backtrace.join("\n")}"
|
38
|
+
ensure
|
39
|
+
schedule if @repeat
|
40
|
+
end
|
41
|
+
|
42
|
+
def now
|
43
|
+
Time.now.to_f
|
44
|
+
end
|
45
|
+
|
46
|
+
def schedule
|
47
|
+
@fire_at = now + @seconds
|
48
|
+
@clock.schedule(self)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/tribe.gemspec
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "tribe"
|
8
|
+
s.version = "0.0.2"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Chad Remesch"]
|
12
|
+
s.date = "2012-06-10"
|
13
|
+
s.description = "Actor Model for Ruby. Can support millions of actors (limited by memory). Currently experimental and not recommended for production."
|
14
|
+
s.email = "chad@remesch.com"
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE.txt",
|
17
|
+
"README.rdoc"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
".rspec",
|
22
|
+
"Gemfile",
|
23
|
+
"Gemfile.lock",
|
24
|
+
"Guardfile",
|
25
|
+
"LICENSE.txt",
|
26
|
+
"README.rdoc",
|
27
|
+
"Rakefile",
|
28
|
+
"VERSION",
|
29
|
+
"lib/tribe.rb",
|
30
|
+
"lib/tribe/actor.rb",
|
31
|
+
"lib/tribe/clock.rb",
|
32
|
+
"lib/tribe/registry.rb",
|
33
|
+
"lib/tribe/scheduler.rb",
|
34
|
+
"lib/tribe/singleton.rb",
|
35
|
+
"lib/tribe/timer.rb",
|
36
|
+
"lib/tribe/worker.rb",
|
37
|
+
"spec/lib/tribe/actor_spec.rb",
|
38
|
+
"spec/lib/tribe/clock_spec.rb",
|
39
|
+
"spec/lib/tribe/registry_spec.rb",
|
40
|
+
"spec/lib/tribe/scheduler_spec.rb",
|
41
|
+
"spec/lib/tribe/timer_spec.rb",
|
42
|
+
"spec/lib/tribe/worker_spec.rb",
|
43
|
+
"spec/spec_helper.rb",
|
44
|
+
"tribe.gemspec"
|
45
|
+
]
|
46
|
+
s.homepage = "http://github.com/chadrem/tribe"
|
47
|
+
s.licenses = ["MIT"]
|
48
|
+
s.require_paths = ["lib"]
|
49
|
+
s.rubygems_version = "1.8.23"
|
50
|
+
s.summary = "Actor Model for Ruby"
|
51
|
+
|
52
|
+
if s.respond_to? :specification_version then
|
53
|
+
s.specification_version = 3
|
54
|
+
|
55
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
56
|
+
s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
|
57
|
+
s.add_development_dependency(%q<rspec>, ["~> 2.10.0"])
|
58
|
+
s.add_development_dependency(%q<bundler>, [">= 1.1.0"])
|
59
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.8.3"])
|
60
|
+
s.add_development_dependency(%q<simplecov>, [">= 0"])
|
61
|
+
s.add_development_dependency(%q<rb-fsevent>, [">= 0"])
|
62
|
+
s.add_development_dependency(%q<guard>, ["~> 1.1.1"])
|
63
|
+
s.add_development_dependency(%q<guard-rspec>, ["~> 1.0.0"])
|
64
|
+
s.add_development_dependency(%q<growl>, [">= 0"])
|
65
|
+
s.add_development_dependency(%q<debugger>, [">= 0"])
|
66
|
+
else
|
67
|
+
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
68
|
+
s.add_dependency(%q<rspec>, ["~> 2.10.0"])
|
69
|
+
s.add_dependency(%q<bundler>, [">= 1.1.0"])
|
70
|
+
s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
|
71
|
+
s.add_dependency(%q<simplecov>, [">= 0"])
|
72
|
+
s.add_dependency(%q<rb-fsevent>, [">= 0"])
|
73
|
+
s.add_dependency(%q<guard>, ["~> 1.1.1"])
|
74
|
+
s.add_dependency(%q<guard-rspec>, ["~> 1.0.0"])
|
75
|
+
s.add_dependency(%q<growl>, [">= 0"])
|
76
|
+
s.add_dependency(%q<debugger>, [">= 0"])
|
77
|
+
end
|
78
|
+
else
|
79
|
+
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
80
|
+
s.add_dependency(%q<rspec>, ["~> 2.10.0"])
|
81
|
+
s.add_dependency(%q<bundler>, [">= 1.1.0"])
|
82
|
+
s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
|
83
|
+
s.add_dependency(%q<simplecov>, [">= 0"])
|
84
|
+
s.add_dependency(%q<rb-fsevent>, [">= 0"])
|
85
|
+
s.add_dependency(%q<guard>, ["~> 1.1.1"])
|
86
|
+
s.add_dependency(%q<guard-rspec>, ["~> 1.0.0"])
|
87
|
+
s.add_dependency(%q<growl>, [">= 0"])
|
88
|
+
s.add_dependency(%q<debugger>, [">= 0"])
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tribe
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -191,15 +191,20 @@ files:
|
|
191
191
|
- VERSION
|
192
192
|
- lib/tribe.rb
|
193
193
|
- lib/tribe/actor.rb
|
194
|
+
- lib/tribe/clock.rb
|
194
195
|
- lib/tribe/registry.rb
|
195
196
|
- lib/tribe/scheduler.rb
|
196
197
|
- lib/tribe/singleton.rb
|
198
|
+
- lib/tribe/timer.rb
|
197
199
|
- lib/tribe/worker.rb
|
198
200
|
- spec/lib/tribe/actor_spec.rb
|
201
|
+
- spec/lib/tribe/clock_spec.rb
|
199
202
|
- spec/lib/tribe/registry_spec.rb
|
200
203
|
- spec/lib/tribe/scheduler_spec.rb
|
204
|
+
- spec/lib/tribe/timer_spec.rb
|
201
205
|
- spec/lib/tribe/worker_spec.rb
|
202
206
|
- spec/spec_helper.rb
|
207
|
+
- tribe.gemspec
|
203
208
|
homepage: http://github.com/chadrem/tribe
|
204
209
|
licenses:
|
205
210
|
- MIT
|
@@ -215,7 +220,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
215
220
|
version: '0'
|
216
221
|
segments:
|
217
222
|
- 0
|
218
|
-
hash:
|
223
|
+
hash: 4061265025390310383
|
219
224
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
220
225
|
none: false
|
221
226
|
requirements:
|