You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
280 lines
6.5 KiB
D
280 lines
6.5 KiB
D
/*
|
|
Monster - an advanced game scripting language
|
|
Copyright (C) 2007-2009 Nicolay Korslund
|
|
Email: <korslund@gmail.com>
|
|
WWW: http://monster.snaptoad.com/
|
|
|
|
This file (threads.d) is part of the Monster script language
|
|
package.
|
|
|
|
Monster is distributed as free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU General Public License
|
|
version 3, as published by the Free Software Foundation.
|
|
|
|
This program is distributed in the hope that it will be useful, but
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
version 3 along with this program. If not, see
|
|
http://www.gnu.org/licenses/ .
|
|
|
|
*/
|
|
|
|
// This module provides an interface to the virtual threading API in
|
|
// Monster.
|
|
|
|
module monster.modules.threads;
|
|
|
|
import monster.monster;
|
|
import monster.vm.mobject;
|
|
import monster.vm.idlefunction;
|
|
import monster.vm.thread;
|
|
import monster.vm.mclass;
|
|
import monster.compiler.functions;
|
|
import std.stdio;
|
|
|
|
const char[] moduleDef =
|
|
"singleton thread;
|
|
|
|
// Used to kill or pause our own or other threads.
|
|
idle kill();
|
|
idle pause();
|
|
|
|
// Get status information about a thread
|
|
native bool isScheduled();
|
|
native bool isPaused();
|
|
native bool isIdle();
|
|
native bool isDead();
|
|
bool isAlive() { return !isDead(); }
|
|
|
|
// Create a new (paused) thread for a given function
|
|
native thread create(function() f);
|
|
|
|
// Schedule a (paused) thread to run the next frame
|
|
native restart();
|
|
|
|
// Call a (paused) thread directly - returns when the thread exits or
|
|
// calls an idle function.
|
|
idle call();
|
|
|
|
// Wait for a thread to finish. Will not return until the thread is
|
|
// dead.
|
|
idle wait();
|
|
|
|
// Start a function as a thread in the background
|
|
thread start(function() f)
|
|
{
|
|
var t = create(f);
|
|
t.restart();
|
|
return t;
|
|
}"; //"
|
|
|
|
/*
|
|
The char[] name stuff above will of course be replaced with real
|
|
function pointers once those are done. When closures are done we
|
|
will also add:
|
|
|
|
function() wrap(function() f)
|
|
{
|
|
var t = create(f);
|
|
return { t.call(); }
|
|
}
|
|
|
|
*/
|
|
|
|
MonsterObject *trdSing;
|
|
|
|
class Kill : IdleFunction
|
|
{
|
|
IS initiate(Thread *t)
|
|
{
|
|
auto mo = params.obj;
|
|
|
|
if(mo !is trdSing)
|
|
{
|
|
// Check if this is another thread
|
|
auto trd = getOwner(mo);
|
|
if(trd !is t)
|
|
{
|
|
// It is. Kill it explicitly and return.
|
|
trd.kill();
|
|
return IS.Return;
|
|
}
|
|
}
|
|
|
|
// If it's our own thread, tell the scheduler to kill it from
|
|
// the inside.
|
|
return IS.Kill;
|
|
}
|
|
}
|
|
|
|
class Pause : IdleFunction
|
|
{
|
|
IS initiate(Thread *t)
|
|
{
|
|
auto mo = params.obj;
|
|
|
|
// Can only run on the singleton object
|
|
if(mo !is trdSing)
|
|
fail("Can only pause our own thread");
|
|
|
|
// Move the thread to the 'paused' list
|
|
t.moveTo(&scheduler.paused);
|
|
|
|
return IS.Manual;
|
|
}
|
|
}
|
|
|
|
Thread *getOwner() { return getOwner(params.obj); }
|
|
Thread *getOwner(MonsterObject *mo)
|
|
{
|
|
assert(mo !is null);
|
|
if(mo is trdSing)
|
|
fail("Cannot run this function on the global singleton thread.");
|
|
auto trd = cast(Thread*) mo.getExtra(_threadClass).vptr;
|
|
assert(trd !is null);
|
|
assert(!trd.isRunning || !trd.isPaused,
|
|
"thread cannot be running and paused at the same time");
|
|
return trd;
|
|
}
|
|
|
|
MonsterObject *createObj(Thread *trd)
|
|
{
|
|
assert(trd !is null);
|
|
auto mo = _threadClass.createObject();
|
|
mo.getExtra(_threadClass).vptr = trd;
|
|
return mo;
|
|
}
|
|
|
|
void create()
|
|
{
|
|
// Can only run on the singleton object
|
|
if(params.obj !is trdSing)
|
|
fail("Can only use create() on the global thread object.");
|
|
|
|
auto fn = stack.popFuncRef();
|
|
auto trd = fn.getObject().thread(fn.getFunction());
|
|
|
|
stack.pushObject(createObj(trd));
|
|
}
|
|
|
|
// Call is used to restore a thread that was previously paused. It
|
|
// will enter the thread immediately, like a normal function call, but
|
|
// it will still run in its own thread. If you only wish to schedule
|
|
// it for later, use restart instead.
|
|
class Call : IdleFunction
|
|
{
|
|
override:
|
|
IS initiate(Thread *t)
|
|
{
|
|
if(params.obj is trdSing)
|
|
fail("Cannot use call() on our own thread.");
|
|
|
|
// Get the thread we're resuming
|
|
auto trd = getOwner();
|
|
|
|
if(trd is t)
|
|
fail("Cannot use call() on our own thread.");
|
|
|
|
if(trd.isDead)
|
|
fail("Cannot call a dead thread.");
|
|
|
|
if(!trd.isPaused)
|
|
fail("Can only use call() on paused threads");
|
|
|
|
// Background the current thread. Move it to the pause list
|
|
// first, so background doesn't inadvertently delete it.
|
|
t.moveTo(&scheduler.paused);
|
|
t.background();
|
|
assert(!t.isDead);
|
|
|
|
// Reenter the thread
|
|
trd.reenter();
|
|
assert(cthread is null);
|
|
|
|
// Put the old thread in the forground again
|
|
t.foreground();
|
|
|
|
// Make the thread transient again
|
|
t.moveTo(&scheduler.transient);
|
|
|
|
// Return to sender
|
|
return IS.Return;
|
|
}
|
|
|
|
void abort(Thread *t)
|
|
{
|
|
fail("Cannot abort thread while it is calling another thread");
|
|
}
|
|
}
|
|
|
|
class Wait : IdleFunction
|
|
{
|
|
override:
|
|
IS initiate(Thread *t)
|
|
{
|
|
if(params.obj is trdSing)
|
|
fail("Cannot use wait on our own thread.");
|
|
|
|
// Get the thread we're resuming
|
|
auto trd = getOwner();
|
|
|
|
if(trd is t)
|
|
fail("Cannot use wait on our own thread.");
|
|
|
|
// Return immediately if the thread is dead
|
|
if(trd.isDead)
|
|
return IS.Return;
|
|
|
|
t.idleData.vptr = trd;
|
|
|
|
return IS.Poll;
|
|
}
|
|
|
|
bool hasFinished(Thread *t)
|
|
{
|
|
return (cast(Thread*)t.idleData.vptr).isDead;
|
|
}
|
|
}
|
|
|
|
void restart()
|
|
{ getOwner().restart(); }
|
|
|
|
void isDead()
|
|
{ stack.pushBool(getOwner().isDead); }
|
|
|
|
void isIdle()
|
|
{ stack.pushBool(getOwner().fstack.isIdle); }
|
|
|
|
void isPaused()
|
|
{ stack.pushBool(getOwner().isPaused); }
|
|
|
|
void isScheduled()
|
|
{ stack.pushBool(getOwner().isScheduled); }
|
|
|
|
MonsterClass _threadClass;
|
|
|
|
void initThreadModule()
|
|
{
|
|
if(_threadClass !is null)
|
|
return;
|
|
|
|
_threadClass = vm.loadString(moduleDef, "thread");
|
|
trdSing = _threadClass.getSing();
|
|
|
|
_threadClass.bind("kill", new Kill);
|
|
_threadClass.bind("call", new Call);
|
|
_threadClass.bind("pause", new Pause);
|
|
_threadClass.bind("wait", new Wait);
|
|
|
|
_threadClass.bind("create", &create);
|
|
_threadClass.bind("restart", &restart);
|
|
|
|
_threadClass.bind("isDead", &isDead);
|
|
_threadClass.bind("isIdle", &isIdle);
|
|
_threadClass.bind("isPaused", &isPaused);
|
|
_threadClass.bind("isScheduled", &isScheduled);
|
|
}
|