Ruby libevent
rubyでlibeventを使うライブラリを書いてみた。
通常のeventも合わせてテスト全然してないお。
環境はruby1.9。1.8でも動くかな。
#include "ruby.h" #include <stdio.h> #include <sys/time.h> #include <event.h> VALUE mRubyEvent, mRubyEventConst, rb_cEvent, rb_cSignalEvent, rb_cTimerEvent; struct eventdata{ struct event ev; VALUE proc; }; static void frb_event_init(VALUE obj){ event_init(); } static void fevent_callback(int fd, short event, void *args) { struct eventdata *data; VALUE obj, proc; obj = (VALUE)args; Data_Get_Struct(obj, struct eventdata, data); proc = data->proc; if(!NIL_P(proc)){ rb_funcall(proc, rb_intern("call"), 0); } } static void free_event(struct eventdata *data) { if(data){ xfree(data); } } static VALUE fevent_alloc(VALUE klass) { return Data_Wrap_Struct(klass, 0, free_event, 0); } //Event.new(fd, type, &block) static VALUE fevent_initialize(int argc, VALUE *argv, VALUE obj) { //event struct eventdata *data; struct event ev; VALUE vident, vtype, vproc; rb_scan_args(argc, argv, "2&", &vident, &vtype, &vproc); int fd = NUM2INT(vident); int type = NUM2INT(vtype); //event_set(&ev, fd, type, fevent_callback, &obj); data = ALLOC(struct eventdata); DATA_PTR(obj) = data; data->ev = ev; data->proc = vproc; //printf("ev %d\n", ev.ev_fd); event_set(&(data->ev), fd, type, fevent_callback, (void *)obj); return obj; } //TimerEvent(timout, &block) static VALUE ftimer_event_initialize(int argc, VALUE *argv, VALUE obj) { //event struct eventdata *data; struct event ev; struct timeval tv; struct timeval *ptv; VALUE vtimeout, vproc; rb_scan_args(argc, argv, "1&", &vtimeout, &vproc); //int sec = NUM2INT(vtimeout); //event_set(&ev, fd, type, fevent_callback, &obj); data = ALLOC(struct eventdata); DATA_PTR(obj) = data; data->ev = ev; data->proc = vproc; //printf("ev %d\n", ev.ev_fd); evtimer_set(&(data->ev), fevent_callback, (void *)obj); evutil_timerclear(&tv); if(!NIL_P(vtimeout)){ tv.tv_sec = INT2FIX(vtimeout); tv.tv_usec = 0; ptv = &time; }else{ ptv = NULL; } event_add(&(data->ev), ptv); return obj; } static VALUE fevent_add(int argc, VALUE *argv, VALUE obj) { struct eventdata *data; struct timeval time; struct timeval *ptime = NULL; VALUE vtimeout; rb_scan_args(argc, argv, "01", &vtimeout); Data_Get_Struct(obj, struct eventdata, data); if(!NIL_P(vtimeout)){ time.tv_sec = INT2FIX(vtimeout); time.tv_usec = 0; ptime = &time; }else{ ptime = NULL; } event_add(&(data->ev), ptime); return Qnil; } static VALUE fevent_del(VALUE obj) { struct eventdata *data; Data_Get_Struct(obj, struct eventdata, data); event_del(&(data->ev)); return Qnil; } static VALUE fevent_abort(VALUE obj) { int result = event_loopbreak(); return INT2FIX(result); } static VALUE frb_event_dispatch(VALUE obj) { int result; result = event_dispatch(); return INT2FIX(result); } void Init_rbevent(void){ mRubyEvent = rb_define_module("RubyEvent"); rb_define_method(mRubyEvent, "init", frb_event_init, 0); rb_define_method(mRubyEvent, "dispatch", frb_event_dispatch, 0); mRubyEventConst = rb_define_module_under(mRubyEvent, "Constants"); //RubyEvent::Constants rb_define_const(mRubyEventConst, "EVLIST_TIMEOUT", INT2FIX(EVLIST_TIMEOUT)); rb_define_const(mRubyEventConst, "EVLIST_INSERTED", INT2FIX(EVLIST_INSERTED)); rb_define_const(mRubyEventConst, "EVLIST_SIGNAL", INT2FIX(EV_SIGNAL)); rb_define_const(mRubyEventConst, "EVLIST_ACTIVE", INT2FIX(EVLIST_ACTIVE)); rb_define_const(mRubyEventConst, "EVLIST_INTERNAL", INT2FIX(EVLIST_INTERNAL)); rb_define_const(mRubyEventConst, "EVLIST_INIT", INT2FIX(EVLIST_INIT)); rb_define_const(mRubyEventConst, "EV_TIMEOUT", INT2FIX(EV_TIMEOUT)); rb_define_const(mRubyEventConst, "EV_READ", INT2FIX(EV_READ)); rb_define_const(mRubyEventConst, "EV_WRITE", INT2FIX(EV_WRITE)); rb_define_const(mRubyEventConst, "EV_SIGNAL", INT2FIX(EV_SIGNAL)); rb_define_const(mRubyEventConst, "EV_PERSIST", INT2FIX(EV_PERSIST)); //#define EVBUFFER_READ 0x01 //#define EVBUFFER_WRITE 0x02 //#define EVBUFFER_EOF 0x10 //#define EVBUFFER_ERROR 0x20 //#define EVBUFFER_TIMEOUT 0x40 //Event rb_cEvent = rb_define_class("Event", rb_cObject); rb_define_alloc_func(rb_cEvent, fevent_alloc); rb_define_method(rb_cEvent, "initialize", fevent_initialize, -1); rb_define_method(rb_cEvent, "add", fevent_add, -1); rb_define_method(rb_cEvent, "delete", fevent_del, 0); //Signal rb_cSignalEvent = rb_define_class("SignalEvent", rb_cObject); rb_define_alloc_func(rb_cSignalEvent, fevent_alloc); rb_define_method(rb_cSignalEvent, "initialize", fevent_initialize, -1); rb_define_method(rb_cSignalEvent, "add", fevent_add, -1); //Timer rb_cTimerEvent = rb_define_class("TimerEvent", rb_cObject); rb_define_alloc_func(rb_cTimerEvent, fevent_alloc); rb_define_method(rb_cTimerEvent, "initialize", ftimer_event_initialize, -1); }
ほぼpyeventとコンパチなAPIにした。
コールバックはブロックで受けるようにした。
サンプル
require 'rbevent' require 'socket' include(RubyEvent) include(RubyEvent::Constants) init(); serv = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0) sockaddr = Socket.sockaddr_in(6000, "127.0.0.1") serv.bind(sockaddr) serv.listen(5) evt = Event.new(serv.to_i, EV_READ){ p "A" } evt.add() dispatch() p "finish"
READ状態になるとAを出力するだけなシンプルな感じ。
なんかイマイチな気もするなあ。
Ruby/Eventみたいにすべきなのかなあ。
むしろEventMachineを見て組み込める感じにした方がいいのか。
そもそもEventMachine全然知らないけどちゃんとmainloop抽象化してあんのかねえ?
これができたらlibevも書いてみようかな。
(gemにあるRevがコンパイルできねーんだもん)