193 lines
5.4 KiB
JavaScript
193 lines
5.4 KiB
JavaScript
|
/*
|
||
|
* C eventloop example (c_eventloop.c).
|
||
|
*
|
||
|
* ECMAScript code to initialize the exposed API (setTimeout() etc) when
|
||
|
* using the C eventloop.
|
||
|
*
|
||
|
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Timers
|
||
|
*/
|
||
|
|
||
|
/*
|
||
|
* Timer API
|
||
|
*/
|
||
|
|
||
|
function setTimeout(func, delay) {
|
||
|
var cb_func;
|
||
|
var bind_args;
|
||
|
var timer_id;
|
||
|
|
||
|
// Delay can be optional at least in some contexts, so tolerate that.
|
||
|
// https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout
|
||
|
if (typeof delay !== 'number') {
|
||
|
if (typeof delay === 'undefined') {
|
||
|
delay = 0;
|
||
|
} else {
|
||
|
throw new TypeError('invalid delay');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (typeof func === 'string') {
|
||
|
// Legacy case: callback is a string.
|
||
|
cb_func = eval.bind(this, func);
|
||
|
} else if (typeof func !== 'function') {
|
||
|
throw new TypeError('callback is not a function/string');
|
||
|
} else if (arguments.length > 2) {
|
||
|
// Special case: callback arguments are provided.
|
||
|
bind_args = Array.prototype.slice.call(arguments, 2); // [ arg1, arg2, ... ]
|
||
|
bind_args.unshift(this); // [ global(this), arg1, arg2, ... ]
|
||
|
cb_func = func.bind.apply(func, bind_args);
|
||
|
} else {
|
||
|
// Normal case: callback given as a function without arguments.
|
||
|
cb_func = func;
|
||
|
}
|
||
|
|
||
|
timer_id = EventLoop.createTimer(cb_func, delay, true /*oneshot*/);
|
||
|
|
||
|
return timer_id;
|
||
|
}
|
||
|
|
||
|
function clearTimeout(timer_id) {
|
||
|
if (typeof timer_id !== 'number') {
|
||
|
throw new TypeError('timer ID is not a number');
|
||
|
}
|
||
|
var success = EventLoop.deleteTimer(timer_id); /* retval ignored */
|
||
|
}
|
||
|
|
||
|
function setInterval(func, delay) {
|
||
|
var cb_func;
|
||
|
var bind_args;
|
||
|
var timer_id;
|
||
|
|
||
|
if (typeof delay !== 'number') {
|
||
|
if (typeof delay === 'undefined') {
|
||
|
delay = 0;
|
||
|
} else {
|
||
|
throw new TypeError('invalid delay');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (typeof func === 'string') {
|
||
|
// Legacy case: callback is a string.
|
||
|
cb_func = eval.bind(this, func);
|
||
|
} else if (typeof func !== 'function') {
|
||
|
throw new TypeError('callback is not a function/string');
|
||
|
} else if (arguments.length > 2) {
|
||
|
// Special case: callback arguments are provided.
|
||
|
bind_args = Array.prototype.slice.call(arguments, 2); // [ arg1, arg2, ... ]
|
||
|
bind_args.unshift(this); // [ global(this), arg1, arg2, ... ]
|
||
|
cb_func = func.bind.apply(func, bind_args);
|
||
|
} else {
|
||
|
// Normal case: callback given as a function without arguments.
|
||
|
cb_func = func;
|
||
|
}
|
||
|
|
||
|
timer_id = EventLoop.createTimer(cb_func, delay, false /*oneshot*/);
|
||
|
|
||
|
return timer_id;
|
||
|
}
|
||
|
|
||
|
function clearInterval(timer_id) {
|
||
|
if (typeof timer_id !== 'number') {
|
||
|
throw new TypeError('timer ID is not a number');
|
||
|
}
|
||
|
EventLoop.deleteTimer(timer_id);
|
||
|
}
|
||
|
|
||
|
function requestEventLoopExit() {
|
||
|
EventLoop.requestExit();
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Socket handling
|
||
|
*
|
||
|
* Ideally this would be implemented more in C than here for more speed
|
||
|
* and smaller footprint: C code would directly maintain the callback state
|
||
|
* and such.
|
||
|
*
|
||
|
* Also for more optimal I/O, the buffer churn caused by allocating and
|
||
|
* freeing a lot of buffer values could be eliminated by reusing buffers.
|
||
|
* Socket reads would then go into a pre-allocated buffer, for instance.
|
||
|
*/
|
||
|
|
||
|
EventLoop.socketListening = {};
|
||
|
EventLoop.socketReading = {};
|
||
|
EventLoop.socketConnecting = {};
|
||
|
|
||
|
EventLoop.fdPollHandler = function(fd, revents) {
|
||
|
var data;
|
||
|
var cb;
|
||
|
var rc;
|
||
|
var acc_res;
|
||
|
|
||
|
//print('activity on fd', fd, 'revents', revents);
|
||
|
|
||
|
if (revents & Poll.POLLIN) {
|
||
|
cb = this.socketReading[fd];
|
||
|
if (cb) {
|
||
|
data = Socket.read(fd); // no size control now
|
||
|
//print('READ', Duktape.enc('jx', data));
|
||
|
if (data.length === 0) {
|
||
|
this.close(fd);
|
||
|
return;
|
||
|
}
|
||
|
cb(fd, data);
|
||
|
} else {
|
||
|
cb = this.socketListening[fd];
|
||
|
if (cb) {
|
||
|
acc_res = Socket.accept(fd);
|
||
|
//print('ACCEPT:', Duktape.enc('jx', acc_res));
|
||
|
cb(acc_res.fd, acc_res.addr, acc_res.port);
|
||
|
} else {
|
||
|
//print('UNKNOWN');
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (revents & Poll.POLLOUT) {
|
||
|
// Connected
|
||
|
cb = this.socketConnecting[fd];
|
||
|
if (cb) {
|
||
|
delete this.socketConnecting[fd];
|
||
|
cb(fd);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ((revents & ~(Poll.POLLIN | Poll.POLLOUT)) !== 0) {
|
||
|
//print('unexpected revents, close fd');
|
||
|
this.close(fd);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
EventLoop.server = function(address, port, cb_accepted) {
|
||
|
var fd = Socket.createServerSocket(address, port);
|
||
|
this.socketListening[fd] = cb_accepted;
|
||
|
this.listenFd(fd, Poll.POLLIN);
|
||
|
}
|
||
|
|
||
|
EventLoop.connect = function(address, port, cb_connected) {
|
||
|
var fd = Socket.connect(address, port);
|
||
|
this.socketConnecting[fd] = cb_connected;
|
||
|
this.listenFd(fd, Poll.POLLOUT);
|
||
|
}
|
||
|
|
||
|
EventLoop.close = function(fd) {
|
||
|
EventLoop.listenFd(fd, 0);
|
||
|
delete this.socketListening[fd];
|
||
|
delete this.socketReading[fd];
|
||
|
delete this.socketConnecting[fd];
|
||
|
Socket.close(fd);
|
||
|
}
|
||
|
|
||
|
EventLoop.setReader = function(fd, cb_read) {
|
||
|
this.socketReading[fd] = cb_read;
|
||
|
this.listenFd(fd, Poll.POLLIN);
|
||
|
}
|
||
|
|
||
|
EventLoop.write = function(fd, data) {
|
||
|
// This simple example doesn't have support for write blocking / draining
|
||
|
if (typeof data === 'string') {
|
||
|
data = new TextEncoder().encode(data);
|
||
|
}
|
||
|
var rc = Socket.write(fd, data);
|
||
|
}
|