methodmissing-channel 1.0.0
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 +51 -0
- data/Rakefile +68 -0
- data/bench/bench.rb +15 -0
- data/bench/ruby_channel.rb +12 -0
- data/channel.gemspec +24 -0
- data/ext/channel/channel.c +115 -0
- data/ext/channel/extconf.rb +12 -0
- metadata +61 -0
data/README
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
Simple native Channel object for Ruby MRI
|
2
|
+
(c) 2009 Lourens Naudé (methodmissing), James Tucker (raggi)
|
3
|
+
|
4
|
+
http://github.com/methodmissing/channel
|
5
|
+
|
6
|
+
This library works with Ruby 1.8 and 1.9 and is a more efficient
|
7
|
+
implementation of the following Ruby code :
|
8
|
+
|
9
|
+
class RubyChannel
|
10
|
+
def initialize
|
11
|
+
@subscribers = []
|
12
|
+
end
|
13
|
+
def subscribe(&b)
|
14
|
+
@subscribers << b
|
15
|
+
self
|
16
|
+
end
|
17
|
+
def <<(o)
|
18
|
+
@subscribers.each { |s| s.call(o) }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
Use cases :
|
23
|
+
|
24
|
+
* In process message dispatch
|
25
|
+
* Parsers and protocols with a message, error and warning channel
|
26
|
+
* Facilitates decoupled notification and callback patterns
|
27
|
+
|
28
|
+
Caveats :
|
29
|
+
|
30
|
+
* Fixed width channel size for very thin, and fast, subscriber management
|
31
|
+
|
32
|
+
Examples :
|
33
|
+
|
34
|
+
counter = 0
|
35
|
+
ch = Channel.new.subscribe{|obj| @counter += obj }
|
36
|
+
ch << 3
|
37
|
+
ch << -2
|
38
|
+
counter #=> 1
|
39
|
+
|
40
|
+
Todo :
|
41
|
+
|
42
|
+
* A deferrable mode that notifies on a background thread
|
43
|
+
* More flexible notification mechanism through integration with http://github.com/methodmissing/callback/tree/master
|
44
|
+
|
45
|
+
To run the test suite:
|
46
|
+
|
47
|
+
rake
|
48
|
+
|
49
|
+
To run the benchmarks:
|
50
|
+
|
51
|
+
rake bench
|
data/Rakefile
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/clean'
|
4
|
+
|
5
|
+
CH_ROOT = 'ext/channel'
|
6
|
+
|
7
|
+
desc 'Default: test'
|
8
|
+
task :default => :test
|
9
|
+
|
10
|
+
desc 'Run channel tests.'
|
11
|
+
Rake::TestTask.new(:test) do |t|
|
12
|
+
t.libs = [CH_ROOT]
|
13
|
+
t.pattern = 'test/test_*.rb'
|
14
|
+
t.verbose = true
|
15
|
+
end
|
16
|
+
task :test => :build
|
17
|
+
|
18
|
+
namespace :build do
|
19
|
+
file "#{CH_ROOT}/channel.c"
|
20
|
+
file "#{CH_ROOT}/extconf.rb"
|
21
|
+
file "#{CH_ROOT}/Makefile" => %W(#{CH_ROOT}/channel.c #{CH_ROOT}/extconf.rb) do
|
22
|
+
Dir.chdir(CH_ROOT) do
|
23
|
+
ruby 'extconf.rb'
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
desc "generate makefile"
|
28
|
+
task :makefile => %W(#{CH_ROOT}/Makefile #{CH_ROOT}/channel.c)
|
29
|
+
|
30
|
+
dlext = Config::CONFIG['DLEXT']
|
31
|
+
file "#{CH_ROOT}/channel.#{dlext}" => %W(#{CH_ROOT}/Makefile #{CH_ROOT}/channel.c) do
|
32
|
+
Dir.chdir(CH_ROOT) do
|
33
|
+
sh 'make' # TODO - is there a config for which make somewhere?
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
desc "compile channel extension"
|
38
|
+
task :compile => "#{CH_ROOT}/channel.#{dlext}"
|
39
|
+
|
40
|
+
task :clean do
|
41
|
+
Dir.chdir(CH_ROOT) do
|
42
|
+
sh 'make clean'
|
43
|
+
end if File.exists?("#{CH_ROOT}/Makefile")
|
44
|
+
end
|
45
|
+
|
46
|
+
CLEAN.include("#{CH_ROOT}/Makefile")
|
47
|
+
CLEAN.include("#{CH_ROOT}/channel.#{dlext}")
|
48
|
+
end
|
49
|
+
|
50
|
+
task :clean => %w(build:clean)
|
51
|
+
|
52
|
+
desc "compile"
|
53
|
+
task :build => %w(build:compile)
|
54
|
+
|
55
|
+
task :install do |t|
|
56
|
+
Dir.chdir(CH_ROOT) do
|
57
|
+
sh 'sudo make install'
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
desc "clean build install"
|
62
|
+
task :setup => %w(clean build install)
|
63
|
+
|
64
|
+
desc "run benchmarks"
|
65
|
+
task :bench do |t|
|
66
|
+
ruby "bench/bench.rb"
|
67
|
+
end
|
68
|
+
task :bench => :build
|
data/bench/bench.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require "benchmark"
|
2
|
+
$:.unshift "."
|
3
|
+
require File.dirname(__FILE__) + "/../ext/channel/channel"
|
4
|
+
require File.dirname(__FILE__) + "/ruby_channel"
|
5
|
+
|
6
|
+
channel = Channel.new(16)
|
7
|
+
16.times{ channel.subscribe{|o| o } }
|
8
|
+
rb_channel = RubyChannel.new
|
9
|
+
16.times{ rb_channel.subscribe{|o| o } }
|
10
|
+
|
11
|
+
TESTS = 10_000
|
12
|
+
Benchmark.bmbm do |results|
|
13
|
+
results.report("Channel#<<") { TESTS.times { channel << :obj } }
|
14
|
+
results.report("RubyChannel#<<") { TESTS.times { rb_channel << :obj } }
|
15
|
+
end
|
data/channel.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "channel"
|
3
|
+
s.version = "1.0.0"
|
4
|
+
s.date = "2009-08-28"
|
5
|
+
s.summary = "Native MRI channel"
|
6
|
+
s.email = "lourens@methodmissing.com"
|
7
|
+
s.homepage = "http://github.com/methodmissing/channel"
|
8
|
+
s.description = "Simple native Channel object for Ruby MRI (1.8.{6,7} and 1.9.2)"
|
9
|
+
s.has_rdoc = true
|
10
|
+
s.authors = ["Lourens Naudé (methodmissing)","James Tucker (raggi)"]
|
11
|
+
s.platform = Gem::Platform::RUBY
|
12
|
+
s.files = %w[
|
13
|
+
README
|
14
|
+
Rakefile
|
15
|
+
bench/bench.rb
|
16
|
+
bench/ruby_channel.rb
|
17
|
+
ext/channel/extconf.rb
|
18
|
+
ext/channel/channel.c
|
19
|
+
channel.gemspec
|
20
|
+
] + Dir.glob('test/*')
|
21
|
+
s.rdoc_options = ["--main", "README"]
|
22
|
+
s.extra_rdoc_files = ["README"]
|
23
|
+
s.extensions << "ext/channel/extconf.rb"
|
24
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
#include "ruby.h"
|
2
|
+
|
3
|
+
typedef struct {
|
4
|
+
int size;
|
5
|
+
int sbs;
|
6
|
+
VALUE *subscribers;
|
7
|
+
} RChannel;
|
8
|
+
|
9
|
+
#define MAX_CHANNEL_SIZE 32
|
10
|
+
#define GetChannelStruct(obj) (Check_Type(obj, T_DATA), (RChannel*)DATA_PTR(obj))
|
11
|
+
|
12
|
+
VALUE rb_cChannel;
|
13
|
+
static ID id_call;
|
14
|
+
|
15
|
+
static void mark_channel(RChannel* ch)
|
16
|
+
{
|
17
|
+
int i;
|
18
|
+
for (i=0; i < ch->sbs; i++) {
|
19
|
+
rb_gc_mark(ch->subscribers[i]);
|
20
|
+
}
|
21
|
+
}
|
22
|
+
|
23
|
+
static void free_channel(RChannel* ch)
|
24
|
+
{
|
25
|
+
xfree(ch);
|
26
|
+
}
|
27
|
+
|
28
|
+
static VALUE channel_alloc _((VALUE));
|
29
|
+
static VALUE
|
30
|
+
channel_alloc( VALUE klass )
|
31
|
+
{
|
32
|
+
VALUE ch;
|
33
|
+
RChannel* chs;
|
34
|
+
ch = Data_Make_Struct(klass, RChannel, mark_channel, free_channel, chs);
|
35
|
+
chs->size = 0;
|
36
|
+
chs->sbs = 0;
|
37
|
+
chs->subscribers = 0;
|
38
|
+
|
39
|
+
return ch;
|
40
|
+
}
|
41
|
+
|
42
|
+
static VALUE
|
43
|
+
rb_channel_new(VALUE ch, int size)
|
44
|
+
{
|
45
|
+
RChannel* chs = GetChannelStruct(ch);
|
46
|
+
if (size < 0 || size > MAX_CHANNEL_SIZE){
|
47
|
+
rb_raise(rb_eArgError, "Invalid channel size!");
|
48
|
+
}
|
49
|
+
if (size == 0) size++;
|
50
|
+
chs->size = size;
|
51
|
+
chs->subscribers = ALLOC_N(VALUE, size);
|
52
|
+
return ch;
|
53
|
+
}
|
54
|
+
|
55
|
+
static VALUE
|
56
|
+
rb_channel_initialize( int argc, VALUE *argv, VALUE ch )
|
57
|
+
{
|
58
|
+
VALUE size;
|
59
|
+
int channel_size;
|
60
|
+
rb_scan_args(argc, argv, "01", &size);
|
61
|
+
channel_size = NIL_P(size) ? 0 : FIX2INT(size);
|
62
|
+
return rb_channel_new(ch, channel_size);
|
63
|
+
}
|
64
|
+
|
65
|
+
static VALUE
|
66
|
+
rb_channel_size( VALUE ch )
|
67
|
+
{
|
68
|
+
RChannel* chs = GetChannelStruct(ch);
|
69
|
+
return INT2FIX(chs->size);
|
70
|
+
}
|
71
|
+
|
72
|
+
static VALUE
|
73
|
+
rb_channel_subscribers( VALUE ch )
|
74
|
+
{
|
75
|
+
RChannel* chs = GetChannelStruct(ch);
|
76
|
+
return INT2FIX(chs->sbs);
|
77
|
+
}
|
78
|
+
|
79
|
+
static VALUE
|
80
|
+
rb_channel_subscribe( int argc, VALUE *argv, VALUE ch )
|
81
|
+
{
|
82
|
+
VALUE cb;
|
83
|
+
RChannel* chs = GetChannelStruct(ch);
|
84
|
+
if (!rb_block_given_p()) rb_raise(rb_eArgError, "Block callback required!");
|
85
|
+
cb = rb_block_proc();
|
86
|
+
chs->subscribers[chs->sbs] = cb;
|
87
|
+
chs->sbs++;
|
88
|
+
return ch;
|
89
|
+
}
|
90
|
+
|
91
|
+
static VALUE
|
92
|
+
rb_channel_push( VALUE ch, VALUE obj )
|
93
|
+
{
|
94
|
+
int i;
|
95
|
+
RChannel* chs = GetChannelStruct(ch);
|
96
|
+
for (i=0; i < chs->sbs; i++) {
|
97
|
+
rb_funcall(chs->subscribers[i],id_call,1,obj);
|
98
|
+
}
|
99
|
+
return ch;
|
100
|
+
}
|
101
|
+
|
102
|
+
void
|
103
|
+
Init_channel()
|
104
|
+
{
|
105
|
+
id_call = rb_intern("call");
|
106
|
+
|
107
|
+
rb_cChannel = rb_define_class("Channel", rb_cObject);
|
108
|
+
rb_define_alloc_func(rb_cChannel, channel_alloc);
|
109
|
+
|
110
|
+
rb_define_method(rb_cChannel,"initialize", rb_channel_initialize, -1);
|
111
|
+
rb_define_method(rb_cChannel,"size", rb_channel_size, 0);
|
112
|
+
rb_define_method(rb_cChannel,"subscribers", rb_channel_subscribers, 0);
|
113
|
+
rb_define_method(rb_cChannel,"subscribe", rb_channel_subscribe, -1);
|
114
|
+
rb_define_method(rb_cChannel,"<<", rb_channel_push, 1);
|
115
|
+
}
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'mkmf'
|
2
|
+
|
3
|
+
def add_define(name)
|
4
|
+
$defs.push("-D#{name}")
|
5
|
+
end
|
6
|
+
|
7
|
+
dir_config('channel')
|
8
|
+
|
9
|
+
add_define 'RUBY19' if have_func('rb_thread_blocking_region') and have_macro('RUBY_UBF_IO', 'ruby.h')
|
10
|
+
add_define 'RUBY18' if have_var('rb_trap_immediate', ['ruby.h', 'rubysig.h'])
|
11
|
+
|
12
|
+
create_makefile('channel')
|
metadata
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: methodmissing-channel
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- "Lourens Naud\xC3\xA9 (methodmissing)"
|
8
|
+
- James Tucker (raggi)
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2009-08-28 00:00:00 -07:00
|
14
|
+
default_executable:
|
15
|
+
dependencies: []
|
16
|
+
|
17
|
+
description: Simple native Channel object for Ruby MRI (1.8.{6,7} and 1.9.2)
|
18
|
+
email: lourens@methodmissing.com
|
19
|
+
executables: []
|
20
|
+
|
21
|
+
extensions:
|
22
|
+
- ext/channel/extconf.rb
|
23
|
+
extra_rdoc_files:
|
24
|
+
- README
|
25
|
+
files:
|
26
|
+
- README
|
27
|
+
- Rakefile
|
28
|
+
- bench/bench.rb
|
29
|
+
- bench/ruby_channel.rb
|
30
|
+
- ext/channel/extconf.rb
|
31
|
+
- ext/channel/channel.c
|
32
|
+
- channel.gemspec
|
33
|
+
has_rdoc: true
|
34
|
+
homepage: http://github.com/methodmissing/channel
|
35
|
+
post_install_message:
|
36
|
+
rdoc_options:
|
37
|
+
- --main
|
38
|
+
- README
|
39
|
+
require_paths:
|
40
|
+
- lib
|
41
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: "0"
|
46
|
+
version:
|
47
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
48
|
+
requirements:
|
49
|
+
- - ">="
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: "0"
|
52
|
+
version:
|
53
|
+
requirements: []
|
54
|
+
|
55
|
+
rubyforge_project:
|
56
|
+
rubygems_version: 1.2.0
|
57
|
+
signing_key:
|
58
|
+
specification_version: 2
|
59
|
+
summary: Native MRI channel
|
60
|
+
test_files: []
|
61
|
+
|