File: watcher.c

package info (click to toggle)
ruby-cool.io 1.9.3-1
  • links: PTS, VCS
  • area: main
  • in suites: forky
  • size: 716 kB
  • sloc: ansic: 6,851; ruby: 1,730; makefile: 6
file content (277 lines) | stat: -rw-r--r-- 7,963 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
/*
 * Copyright (C) 2007-10 Tony Arcieri
 * You may redistribute this under the terms of the Ruby license.
 * See LICENSE for details
 */

#include "ruby.h"
#include "ev_wrap.h"

#include "cool.io.h"

static VALUE mCoolio = Qnil;
static VALUE cCoolio_Watcher = Qnil;

static VALUE Coolio_Watcher_allocate(VALUE klass);
static void Coolio_Watcher_mark(void *data);

static VALUE Coolio_Watcher_initialize(VALUE self);
static VALUE Coolio_Watcher_attach(VALUE self, VALUE loop);
static VALUE Coolio_Watcher_detach(VALUE self);
static VALUE Coolio_Watcher_enable(VALUE self);
static VALUE Coolio_Watcher_disable(VALUE self);
static VALUE Coolio_Watcher_evloop(VALUE self);
static VALUE Coolio_Watcher_attached(VALUE self);
static VALUE Coolio_Watcher_enabled(VALUE self);

/* 
 * Watchers are Coolio's event observers.  They contain a set of callback
 * methods prefixed by on_* which fire whenever events occur.
 *
 * In order for a watcher to fire events it must be attached to a running
 * loop.  Every watcher has an attach and detach method to control which
 * loop it's associated with.
 *
 * Watchers also have an enable and disable method.  This allows a watcher
 * to temporarily ignore certain events while remaining attached to a given
 * loop.  This is good for watchers which need to be toggled on and off.
 */
void Init_coolio_watcher()
{
  mCoolio = rb_define_module("Coolio");
  cCoolio_Watcher = rb_define_class_under(mCoolio, "Watcher", rb_cObject);
  rb_define_alloc_func(cCoolio_Watcher, Coolio_Watcher_allocate);

  rb_define_method(cCoolio_Watcher, "initialize", Coolio_Watcher_initialize, 0);
  rb_define_method(cCoolio_Watcher, "attach", Coolio_Watcher_attach, 1);
  rb_define_method(cCoolio_Watcher, "detach", Coolio_Watcher_detach, 0);
  rb_define_method(cCoolio_Watcher, "enable", Coolio_Watcher_enable, 0);
  rb_define_method(cCoolio_Watcher, "disable", Coolio_Watcher_disable, 0);
  rb_define_method(cCoolio_Watcher, "evloop", Coolio_Watcher_evloop, 0);
  rb_define_method(cCoolio_Watcher, "attached?", Coolio_Watcher_attached, 0);
  rb_define_method(cCoolio_Watcher, "enabled?", Coolio_Watcher_enabled, 0);
}

static const rb_data_type_t Coolio_Watcher_type = {
  "Coolio::Watcher",
  {
    Coolio_Watcher_mark,
    RUBY_DEFAULT_FREE,
  },
};

struct Coolio_Watcher *Coolio_Watcher_ptr(VALUE watcher)
{
  struct Coolio_Watcher *watcher_data;

  TypedData_Get_Struct(watcher, struct Coolio_Watcher, &Coolio_Watcher_type, watcher_data);
  return watcher_data;
}

static VALUE Coolio_Watcher_allocate(VALUE klass)
{
  struct Coolio_Watcher *watcher_data;
  VALUE watcher = TypedData_Make_Struct(klass, struct Coolio_Watcher, &Coolio_Watcher_type, watcher_data);

  watcher_data->loop = Qnil;
  watcher_data->enabled = 0;

  return watcher;
}

static void Coolio_Watcher_mark(void *data)
{
  struct Coolio_Watcher *watcher_data = data;

  if(watcher_data->loop != Qnil)
    rb_gc_mark(watcher_data->loop);
}

static VALUE Coolio_Watcher_initialize(VALUE self)
{
  rb_raise(rb_eRuntimeError, "watcher base class should not be initialized directly");
}

/**
 *  call-seq:
 *    Coolio::Watcher.attach(loop) -> Coolio::Watcher
 * 
 * Attach the watcher to the given Coolio::Loop.  If the watcher is already attached
 * to a loop, detach it from the old one and attach it to the new one.
 */
static VALUE Coolio_Watcher_attach(VALUE self, VALUE loop)
{
  VALUE loop_watchers, active_watchers;
  struct Coolio_Watcher *watcher_data;

  watcher_data = Coolio_Watcher_ptr(self);
  watcher_data->enabled = 1;
    
  loop_watchers = rb_iv_get(loop, "@watchers");

  if(loop_watchers == Qnil) {
    /* we should never get here */
    loop_watchers = rb_hash_new();
    rb_iv_set(loop, "@watchers", loop_watchers);
  }

  /* Add us to the loop's array of active watchers.  This is mainly done
   * to keep the VM from garbage collecting watchers that are associated
   * with a loop (and also lets you see within Ruby which watchers are
   * associated with a given loop), but isn't really necessary for any 
   * other reason */
  rb_hash_aset(loop_watchers, self, Qtrue);

  active_watchers = rb_iv_get(loop, "@active_watchers");
  if(active_watchers == Qnil)
    active_watchers = INT2NUM(1);
  else
    active_watchers = INT2NUM(NUM2INT(active_watchers) + 1);
  rb_iv_set(loop, "@active_watchers", active_watchers);

  return self;
}

/**
 *  call-seq:
 *    Coolio::Watcher.detach -> Coolio::Watcher
 * 
 * Detach the watcher from its current Coolio::Loop.
 */
static VALUE Coolio_Watcher_detach(VALUE self)
{
  struct Coolio_Watcher *watcher_data;
  struct Coolio_Loop *loop_data;
  VALUE loop_watchers;
  int i;

  watcher_data = Coolio_Watcher_ptr(self);

  if(watcher_data->loop == Qnil)
    rb_raise(rb_eRuntimeError, "not attached to a loop");

  loop_watchers = rb_iv_get(watcher_data->loop, "@watchers");

  /* Remove us from the loop's array of active watchers.  This likely
   * has negative performance and scalability characteristics as this
   * isn't an O(1) operation.  Hopefully there's a better way...
   * Trying a hash for now... */
  rb_hash_delete(loop_watchers, self);

  if(watcher_data->enabled) {
    rb_iv_set(
        watcher_data->loop, 
        "@active_watchers",
        INT2NUM(NUM2INT(rb_iv_get(watcher_data->loop, "@active_watchers")) - 1)
    );
  }

  watcher_data->enabled = 0;

  loop_data = Coolio_Loop_ptr(watcher_data->loop);

  /* Iterate through the events in the loop's event buffer.  If there
   * are any pending events from this watcher, mark them NULL.  The
   * dispatch loop will skip them.  This prevents watchers earlier
   * in the event buffer from detaching others which may have pending
   * events in the buffer but get garbage collected in the meantime */
  for(i = 0; i < loop_data->events_received; i++) {
    if(loop_data->eventbuf[i].watcher == self)
      loop_data->eventbuf[i].watcher = Qnil;
  }

  watcher_data->loop = Qnil;

  return self;
}

/**
 *  call-seq:
 *    Coolio::Watcher.enable -> Coolio::Watcher
 * 
 * Re-enable a watcher which has been temporarily disabled.  See the
 * disable method for a more thorough explanation.
 */
static VALUE Coolio_Watcher_enable(VALUE self)
{
  struct Coolio_Watcher *watcher_data;
  watcher_data = Coolio_Watcher_ptr(self);

  if(watcher_data->enabled)
    rb_raise(rb_eRuntimeError, "already enabled");

  watcher_data->enabled = 1;

  rb_iv_set(
      watcher_data->loop, 
      "@active_watchers",
      INT2NUM(NUM2INT(rb_iv_get(watcher_data->loop, "@active_watchers")) + 1)
  );

  return self;
}

/**
 *  call-seq:
 *    Coolio::Watcher.disable -> Coolio::Watcher
 * 
 * Temporarily disable an event watcher which is attached to a loop.  
 * This is useful if you wish to toggle event monitoring on and off.  
 */
static VALUE Coolio_Watcher_disable(VALUE self)
{
  struct Coolio_Watcher *watcher_data;
  watcher_data = Coolio_Watcher_ptr(self);

  if(!watcher_data->enabled)
    rb_raise(rb_eRuntimeError, "already disabled");

  watcher_data->enabled = 0;

  rb_iv_set(
      watcher_data->loop, 
      "@active_watchers",
      INT2NUM(NUM2INT(rb_iv_get(watcher_data->loop, "@active_watchers")) - 1)
  );

  return self;
}

/**
 *  call-seq:
 *    Coolio::Watcher.evloop -> Coolio::Loop
 * 
 * Return the loop to which we're currently attached
 */
static VALUE Coolio_Watcher_evloop(VALUE self)
{
  struct Coolio_Watcher *watcher_data;

  watcher_data = Coolio_Watcher_ptr(self);
  return watcher_data->loop;
}

/**
 *  call-seq:
 *    Coolio::Watcher.attached? -> Boolean
 * 
 * Is the watcher currently attached to an event loop?
 */
static VALUE Coolio_Watcher_attached(VALUE self)
{
  return Coolio_Watcher_evloop(self) != Qnil;
}

/**
 *  call-seq:
 *    Coolio::Watcher.enabled? -> Boolean
 * 
 * Is the watcher currently enabled?
 */
static VALUE Coolio_Watcher_enabled(VALUE self)
{
	struct Coolio_Watcher *watcher_data;
  watcher_data = Coolio_Watcher_ptr(self);
  
	return watcher_data->enabled ? Qtrue : Qfalse;
}