Doge log

Abby CTO 雑賀 力王のオフィシャルサイトです

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がコンパイルできねーんだもん)