As I'm working on the next release of Sqrat (sorry it's taking so long guys! I haven't forgotten about it!) I've run into a concept that would be very helpful to me personally and am curious if anyone else in the community would use it.
Essentially, it's just a scheduler like the one in Stackless Python. Very simple, round robin, geared towards coroutines. I've got a working example below, coded in pure squirrel, but I think that the final product would be in the form of native routines. I'd also like to get a system like stackless' channels in there too.
I would probably package them and any other utilities/libraries like this that I build in the future as part of Sqrat but keep it separate from the binding libs (maybe SqratEx?) Honestly, I would really like to see this and a couple of other concepts become part of the core language, but I think Alberto has his hands full with Squirrel 3 right now :)
In any case, I'd like some feedback on 1) if anyone else thinks they would use this, 2) if you have opinions about how the interface should be set up or how the internals should work, and 3) if there are any other utilities like this that you feel would be a good fit.
Thanks!
====================================================================
//
// Scheduling Example
//
sqrat <- {
taskList = [] // Array of active or scheduled threads
sleepCount = 0 // How many threads are currently sleeping
running = false;
// Add a closure to be scheduled
function schedule(closure) {
local t = {
thread = ::newthread(closure)
vars = []
started = false
}
taskList.push(t);
return function(...) : (t) {
for(local i = 0; i< vargc; i++) {
t.vars.push(vargv
);
}
}
}
// Start Scheduler (Simple Round Robin)
function run() {
if(running) { return; } // Prevent the scheduler from running twice in different threads
running = true;
do {
foreach(idx, task in taskList) {
if(!task.started) {
task.started = true;
// TODO: Native function that converts array to var args, allow for any count of variables.
switch(task.vars.len()) {
case 0: task.thread.call(); break;
case 1: task.thread.call(task.vars[0]); break;
case 2: task.thread.call(task.vars[0], task.vars[1]); break;
case 3: task.thread.call(task.vars[0], task.vars[1], task.vars[2]); break;
case 4: task.thread.call(task.vars[0], task.vars[1], task.vars[2], task.vars[3]); break;
case 5: task.thread.call(task.vars[0], task.vars[1], task.vars[2], task.vars[3], task.vars[4]); break;
case 6: task.thread.call(task.vars[0], task.vars[1], task.vars[2], task.vars[3], task.vars[4], task.vars[5]); break;
case 7: task.thread.call(task.vars[0], task.vars[1], task.vars[2], task.vars[3], task.vars[4], task.vars[5], task.vars
); break;
case 8: task.thread.call(task.vars[0], task.vars[1], task.vars[2], task.vars[3], task.vars[4], task.vars[5], task.vars
, task.vars[7]); break;
case 9: task.thread.call(task.vars[0], task.vars[1], task.vars[2], task.vars[3], task.vars[4], task.vars[5], task.vars
, task.vars[7], task.vars
); break;
}
} else {
if(task.thread.getstatus() == "suspended") {
task.thread.wakeup();
} else {
taskList.remove(idx); // Remove a thread from the scheduler when done
}
}
}
if(sleepCount >= taskList.len()) {
// If all threads are sleeping, we probably want to yield some time to the OS, such as a sleep(1)
//yieldToSystem(); // This function doesn't exist yet!
}
sleepCount = 0;
} while( taskList.len() > 0 ); // Exit when we've run out of threads to schedule
running = false;
}
// Suspend a thread for at least "timeout" seconds
function sleep(timeout) {
local begin = clock(); // I would like to measure time in ms
local now;
do {
sleepCount += 1;
::suspend();
now = clock();
} while( (now - begin) < timeout ); // Loop an suspend until at least the specified time has passed (may be more!)
}
function newchannel() {
local c = {
msgQueue = []
rcvQueue = []
function send(msg) {
msgQueue.push(msg);
if(rcvQueue.len()) {
rcvr = rcvQueue.top();
rcvQueue.remove(0);
rcvr.wakeup(msg);
}
}
function receive() {
}
}
return c;
}
}
//
// Usage
//
function timedPrint(str, timeout) {
for(local i = 0; i < 100; i++) {
::print(str + ": " + i + "\n");
sqrat.sleep(timeout); // Sleep requires regular wakeups to be at all accurate. Works best when used with the scheduler
}
}
function ping() {
::print("Ping\n");
sqrat.sleep(1);
sqrat.schedule(pong); // New closures can be scheduled while the scheduler is running
}
function pong() {
::print("Pong\n");
sqrat.sleep(1);
sqrat.schedule(ping);
}
// Works with anonymous functions too!
sqrat.schedule(function(x) {
::print("Countdown!\n");
for(local i = 0; i < x; i++) {
::print(i + "...\n");
::suspend();
}
})(10);
sqrat.schedule(timedPrint)("Tick", 5);
sqrat.schedule(ping);
sqrat.run(); // Will run indefinately due to the infinate ping/pong