Updated and tested with latest Monster version. Fps ticker and test

script are now simple function-scripts, not classes.


git-svn-id: https://openmw.svn.sourceforge.net/svnroot/openmw/trunk@82 ea6a568a-9f4f-0410-981a-c910a81bb256
pull/7/head
nkorslund 16 years ago
parent 879cc132d5
commit 656007cf01

@ -381,8 +381,6 @@ struct Assembler
addi(func);
}
void halt() { cmd(BC.Halt); }
void newObj(int i)
{
cmd(BC.New);

@ -68,8 +68,6 @@ enum BC
// index must also be -1, and the class index
// is ignored.
Halt, // Halt execution. Only allowed in state code.
New, // Create a new object. Followed by an int
// giving the class index (in the file lookup
// table)
@ -508,7 +506,6 @@ char[][] bcToString =
BC.ReturnVal: "ReturnVal",
BC.ReturnValN: "ReturnValN",
BC.State: "State",
BC.Halt: "Halt",
BC.New: "New",
BC.Jump: "Jump",
BC.JumpZ: "JumpZ",

@ -50,8 +50,8 @@ import monster.vm.mobject;
import monster.vm.idlefunction;
import monster.vm.mclass;
import monster.vm.error;
import monster.vm.fstack;
import monster.vm.thread;
import monster.vm.stack;
import monster.vm.vm;
import std.stdio;
@ -78,10 +78,8 @@ typedef extern(C) void function() c_callback;
struct Function
{
// These three variables (owner, lines and bcode) are common between
// Function and State. They MUST be placed and ordered equally in
// both structs because we're use some unsafe pointer trickery.
MonsterClass owner;
MonsterClass owner; // Must be the first entry
LineSpec[] lines; // Line specifications for byte code
union
{
@ -91,8 +89,8 @@ struct Function
c_callback natFunc_c;
IdleFunction idleFunc; // Idle function callback
}
Token name;
Type type; // Return type
FuncType ftype; // Function type
Variable* params[]; // List of parameters
@ -128,48 +126,49 @@ struct Function
// this is a function that takes a variable number of arguments.
bool isVararg() { return params.length && params[$-1].isVararg; }
// It would be cool to have a template version of call that took and
// returned the correct parameters, and could even check
// them. However, doing generic type inference is either very
// dangerous or would involve complicated checks (think basing an
// ulong or a a byte to float parameters.) The best thing to do is
// to handle types at the binding stage, allowing things like
// bind!(int,float,int)("myfunc", myfunc) - does type checking for
// you. A c++ version could be much more difficult to handle, and
// might rely more on manual coding.
// This is used to call the given function from native code. Note
// that this is used internally for native functions, but not for
// any other type. Idle functions can NOT be called directly from
// native code.
void call(MonsterObject *obj)
// native code. Returns the thread, which is never null but might be
// dead.
Thread *call(MonsterObject *obj)
{
assert(obj !is null || isStatic);
// Make sure there's a thread to use
bool wasNew;
if(cthread is null)
{
wasNew = true;
cthread = Thread.getNew();
// Get a new thread and put it in the foreground
auto tr = Thread.getNew();
tr.foreground();
assert(tr is cthread);
}
assert(cthread !is null);
assert(!cthread.isDead);
bool wasEmpty = cthread.fstack.isEmpty;
// Push the function on the stack
fstack.push(this, obj);
cthread.fstack.push(this, obj);
switch(ftype)
{
case FuncType.NativeDDel:
natFunc_dg();
break;
goto pop;
case FuncType.NativeDFunc:
natFunc_fn();
break;
goto pop;
case FuncType.NativeCFunc:
natFunc_c();
pop:
// Remove ourselves from the function stack
cthread.fstack.pop();
break;
case FuncType.Normal:
cthread.execute();
assert(!cthread.shouldExit,
"shouldExit should only be set for state threads");
// Execute will pop itself if necessary
break;
case FuncType.Native:
fail("Called unimplemented native function " ~ toString);
@ -181,41 +180,38 @@ struct Function
assert(0, "unknown FuncType for " ~ toString);
}
// Remove ourselves from the function stack
fstack.pop();
// If we started at the bottom of the function stack, put the
// thread in the background now. This will automatically delete
// the thread if it's not used.
assert(cthread !is null);
// Reset cthread, if we created it.
if(wasNew)
auto ct = cthread;
if(wasEmpty)
{
// If the thread is still in the unused list, we can safely
// kill it.
if(cthread.isUnused)
cthread.kill();
else
// Otherwise, store the stack
cthread.acquireStack();
cthread = null;
if(cthread.fstack.isEmpty && stack.getPos != 0)
{
assert(cthread.isTransient);
assert(fstack.isEmpty);
// We have to do some trickery to retain the stack in
// cases where the function exits completely.
cthread = null; // This will prevent kill() from clearing
// the stack.
ct.kill();
}
else
cthread.background();
}
// I think we could also check fstack if it's empty instead of
// using wasNew. Leave these checks here to see if this assumtion
// is correct.
else assert(!fstack.isEmpty);
return ct;
}
// Call without an object. TODO: Only allowed for functions compiled
// without a class using compile() below, but in the future this
// will be replaced with a static function.
void call()
// without a class using compile() below, but in the future it will
// be allowed for static function.
Thread* call()
{
assert(owner is int_mc);
assert(owner !is null);
assert(int_mo !is null);
call(int_mo);
return call(int_mo);
}
// This allows you to compile a function file by writing fn =
@ -662,11 +658,12 @@ class FuncDeclaration : Statement
fail(format("Cannot override %s, parameter types do not match",
o.toString), fn.name.loc);
// There's no big technical reason why this shouldn't be
// possible, but we haven't tested it or thought about it
// yet.
if(o.isIdle || fn.isIdle)
fail("Cannot override idle functions", fn.name.loc);
if(o.isIdle && !fn.isIdle)
fail("Cannot override idle function " ~ o.name.str ~
" with a non-idle function", fn.name.loc);
if(!o.isIdle && fn.isIdle)
fail("Cannot override normal function " ~ o.name.str ~
" with an idle function", fn.name.loc);
}
else
{
@ -805,11 +802,6 @@ class FunctionCallExpr : MemberExpression
fail("Undefined function "~name.str, name.loc);
}
// Is this an idle function?
if(fd.isIdle && !sc.isStateCode)
fail("Idle functions can only be called from state code",
name.loc);
type = fd.type;
assert(type !is null);
@ -933,6 +925,7 @@ class FunctionCallExpr : MemberExpression
{
recurse = false;
dotImport.evalAsm();
recurse = true;
return;
}
@ -944,5 +937,8 @@ class FunctionCallExpr : MemberExpression
tasm.callIdle(fd.index, fd.owner.getTreeIndex(), isMember);
else
tasm.callFunc(fd.index, fd.owner.getTreeIndex(), isMember);
// Reset pdone incase the expression is evaluated again
pdone = false;
}
}

@ -112,8 +112,8 @@ class FloatingProperties(T) : NumericProperties!(T)
inserts("mant_bits", "int", { tasm.push(T.mant_dig); });
inserts("mant_dig", "int", { tasm.push(T.mant_dig); });
// Lets add in number of bits in the exponent as well
inserts("exp_bits", "int", { tasm.push(8*T.sizeof-T.mant_dig); });
// Lets add in number of bits in the exponent as well.
inserts("exp_bits", "int", { tasm.push(cast(uint)(8*T.sizeof-T.mant_dig)); });
}
}

@ -438,37 +438,6 @@ class ContinueBreakStatement : Statement
}
}
// halt; stops the execution of state code (without changing the
// state)
class HaltStatement : Statement
{
static bool canParse(TokenArray toks)
{
return isNext(toks, TT.Halt);
}
void parse(ref TokenArray toks)
{
if(!isNext(toks, TT.Halt, loc))
assert(0, "Internal error");
if(!isNext(toks, TT.Semicolon))
fail("halt expected ;", toks);
}
void resolve(Scope sc)
{
if(!sc.isStateCode)
fail("Halt statements are only allowed in state code", loc);
}
void compile()
{
setLine();
tasm.halt();
}
}
// do-while loop (might also support do-until loops in the future)
// do statement while (expression)
// do : label statement while (expression)
@ -1396,9 +1365,19 @@ class ReturnStatement : Statement
void resolve(Scope sc)
{
/*
// Not allowed in state code.
if(sc.isStateCode)
fail("return not allowed in state code", loc);
*/
// Return in state code is handled as a special case
if(sc.isStateCode)
{
assert(!sc.isInFunc);
if(exp !is null)
fail("Cannot return an expression in state code", loc);
return;
}
// Store the number of local variables we have to pop of the
// stack
@ -1437,13 +1416,15 @@ class ReturnStatement : Statement
void compile()
{
setLine();
if(exp !is null)
if(exp !is null && fn !is null)
{
assert(!fn.type.isVoid);
exp.eval();
// Return an expression
tasm.exit(locals, exp.type.getSize);
}
else
// Return without an expression
tasm.exit(locals);
}
}
@ -1483,7 +1464,6 @@ class CodeBlock : Statement
else if(WhileStatement.canParse(toks)) b = new WhileStatement;
else if(ForStatement.canParse(toks)) b = new ForStatement;
else if(StateStatement.canParse(toks)) b = new StateStatement;
else if(HaltStatement.canParse(toks)) b = new HaltStatement;
else if(LabelStatement.canParse(toks)) b = new LabelStatement;
else if(GotoStatement.canParse(toks)) b = new GotoStatement;
else if(ContinueBreakStatement.canParse(toks)) b = new ContinueBreakStatement;
@ -1607,9 +1587,9 @@ class CodeBlock : Statement
setLine();
// If this is the main block at the state level, we must finish
// it with a halt instruction.
// it with an exit instruction.
if(isState)
tasm.halt();
tasm.exit();
// Local variables are forbidden in any state block, no matter
// at what level they are

@ -38,13 +38,12 @@ import std.stdio;
struct State
{
// These three variables (owner, lines and bcode) are common between
// Function and State. They MUST be placed and ordered equally in
// both structs because we're use some unsafe pointer trickery.
MonsterClass owner;
MonsterClass owner; // This must be the first entry, since we're
// using some pointer trickery with Function and
// State.
LineSpec[] lines; // Line specifications for byte code
ubyte[] bcode; // Final compiled code
Token name;
int index;

@ -100,11 +100,11 @@ enum TT
Return,
Switch, Select,
State,
Struct, Enum, Thread,
Struct, Enum,
Import, Clone, Override, Final, Function, With,
This, New, Static, Const, Out, Ref, Abstract, Idle,
Public, Private, Protected, True, False, Native, Null,
Goto, Halt, Var, In,
Goto, Var, In,
Last, // Tokens after this do not have a specific string
// associated with them.
@ -224,7 +224,6 @@ const char[][] tokenList =
TT.State : "state",
TT.Struct : "struct",
TT.Enum : "enum",
TT.Thread : "thread",
TT.Import : "import",
TT.Typeof : "typeof",
TT.Singleton : "singleton",
@ -247,7 +246,6 @@ const char[][] tokenList =
TT.Native : "native",
TT.Null : "null",
TT.Goto : "goto",
TT.Halt : "halt",
TT.Var : "var",
TT.In : "in",

@ -3,10 +3,12 @@ module monster.modules.all;
import monster.modules.io;
import monster.modules.timer;
import monster.modules.frames;
import monster.modules.threads;
void initAllModules()
{
initIOModule();
initTimerModule();
initFramesModule();
initThreadModule;
}

@ -19,7 +19,7 @@ float totalTime; // Time since rendering started
ulong counter; // Number of frames since program startup
// Sleep a given number of frames
idle sleep(int frameNum);
idle fsleep(int frameNum);
"; //"
// Keep local copies of these, since we don't want Monster code to
@ -54,13 +54,13 @@ void updateFrames(float time, int frmCount = 1)
class IdleFrameSleep : IdleFunction
{
override:
bool initiate(Thread* cn)
IS initiate(Thread* cn)
{
// Calculate the return frame
cn.idleData.l = frames + stack.popInt;
// Schedule us
return true;
return IS.Poll;
}
bool hasFinished(Thread* cn)
@ -78,7 +78,7 @@ void initFramesModule()
mc = new MonsterClass(MC.String, moduleDef, "frames");
// Bind the idle
mc.bind("sleep", new IdleFrameSleep);
mc.bind("fsleep", new IdleFrameSleep);
// Get pointers to the variables so we can write to them easily.
auto mo = mc.getSing();

@ -0,0 +1,9 @@
module frames;
float time; // Time since last frame
float totalTime; // Time since rendering started
ulong counter; // Number of frames since program startup
// Sleep a given number of frames
idle fsleep(int frameNum);

@ -0,0 +1,42 @@
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(char[] name);
// 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 resume();
// Wait for a thread to finish. Will not return until the thread is
// dead.
idle wait();
// Call a function as a thread
thread call(char[] name)
{
var t = create(name);
t.resume();
return t;
}
// Start a function as a thread in the background
thread start(char[] name)
{
var t = create(name);
t.restart();
return t;
}

@ -1,25 +1,284 @@
// This module provides an interface to the virtual threading API in
// Monster. Not done.
// Monster.
module monster.modules.threads;
/*
import monster.monster;
import std.stdio;
const char[] moduleDef =
"singleton thread;
native cancel();
native schedule();
// 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(char[] name);
// 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 resume();
// Wait for a thread to finish. Will not return until the thread is
// dead.
idle wait();
// Call a function as a thread
thread call(char[] name)
{
var t = create(name);
t.resume();
return t;
}
// Start a function as a thread in the background
thread start(char[] name)
{
var t = create(name);
t.restart();
return t;
}
"; //"
void initThreadModule()
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()
{
static MonsterClass mc;
// Can only run on the singleton object
if(params.obj !is trdSing)
fail("Can only use create() on the global thread object.");
char[] name = stack.popString8();
assert(cthread !is null);
// This is a dirty hack that's only needed because we haven't
// implemented function pointers yet. Gets the caller object.
assert(cthread.fstack.list.length >= 2);
auto nd = cthread.fstack.cur;
nd = cthread.fstack.list.getNext(nd);
if(nd.getCls() is _threadClass)
nd = cthread.fstack.list.getNext(nd);
auto mo = nd.obj;
// Find the function
auto fn = mo.cls.findFunction(name);
if(mc !is null)
if(fn.paramSize > 0)
fail("create(): function " ~ name ~ " cannot have parameters");
// Create a new thread
Thread *trd = Thread.getNew();
// Schedule the thread run the next frame
trd.pushFunc(fn, mo);
assert(trd.isPaused);
// This will mess with the stack frame though, so set it up
// correctly.
trd.fstack.cur.frame = stack.getStartInt(0);
cthread.fstack.restoreFrame();
stack.pushObject(createObj(trd));
}
// Resume is used to restore a thread that was previously paused. It
// will enter the thread immediately, like call. If you wish to run it
// later, use restart instead.
class Resume : IdleFunction
{
override:
IS initiate(Thread *t)
{
if(params.obj is trdSing)
fail("Cannot use resume() on our own thread.");
// Get the thread we're resuming
auto trd = getOwner();
if(trd is t)
fail("Cannot use resume() on our own thread.");
if(trd.isDead)
fail("Cannot resume a dead thread.");
if(!trd.isPaused)
fail("Can only use resume() 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()
{
auto trd = getOwner();
if(trd.isDead)
fail("Cannot restart a dead thread");
if(!trd.isPaused)
fail("Can only use restart() on paused threads");
// Move to the runlist
trd.moveTo(scheduler.runNext);
}
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;
mc = new MonsterClass(MC.String, moduleDef, "thread");
_threadClass = new MonsterClass(MC.String, moduleDef, "thread");
trdSing = _threadClass.getSing();
_threadClass.bind("kill", new Kill);
_threadClass.bind("resume", new Resume);
_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);
}
*/

@ -26,7 +26,7 @@ idle sleep(float secs);
class IdleSleep_SystemClock : IdleFunction
{
override:
bool initiate(Thread* cn)
IS initiate(Thread* cn)
{
// Get the parameter
float secs = stack.popFloat;
@ -38,7 +38,7 @@ class IdleSleep_SystemClock : IdleFunction
cn.idleData.l += secs*TicksPerSecond;
// Schedule us
return true;
return IS.Poll;
}
bool hasFinished(Thread* cn)
@ -56,7 +56,7 @@ class IdleSleep_SystemClock : IdleFunction
class IdleSleep_Timer : IdleFunction
{
override:
bool initiate(Thread* cn)
IS initiate(Thread* cn)
{
// The timer is stored in the object's 'extra' pointer
auto t = cast(SleepManager)cn.extraData.obj;
@ -66,7 +66,7 @@ class IdleSleep_Timer : IdleFunction
cn.idleData.l = t.current + cast(long)(t.tickSize*stack.popFloat);
// Schedule us
return true;
return IS.Poll;
}
bool hasFinished(Thread* cn)

@ -391,9 +391,14 @@ struct FreeList(T)
// Get the first element in the list
T* getHead() { return &nodes.getHead().value; }
// Loop through the structs in this list
int opApply(int delegate(ref T) dg)
// Get the next element in the list
T* getNext(T* nd)
{
return nodes.opApply(dg);
auto node = cast(TNode*)nd;
return cast(T*) node.getNext();
}
// Loop through the structs in this list
int opApply(int delegate(ref T) dg)
{ return nodes.opApply(dg); }
}

@ -28,17 +28,22 @@ import monster.vm.mobject;
import monster.vm.mclass;
import monster.vm.stack;
import monster.vm.error;
import monster.vm.thread;
import monster.compiler.states;
import monster.compiler.functions;
import monster.compiler.linespec;
import monster.util.freelist;
import std.stdio;
import std.string;
// "friendly" parameter and stack handling.
enum SPType
{
Function, // A function (script or native)
Idle, // Idle function
State, // State code
NConst, // Native constructor
}
// One entry in the function stack
@ -59,7 +64,7 @@ struct StackPoint
// Could have an afterStack to check that the function has the
// correct imprint (corresponding to an imprint-var in Function.)
int *frame; // Stack frame, stored when entering the function
int *frame; // Stack frame for this function
// Get the class owning the function
MonsterClass getCls()
@ -78,6 +83,15 @@ struct StackPoint
bool isState()
{ return ftype == SPType.State; }
bool isIdle()
{ return ftype == SPType.Idle; }
bool isNative()
{ return isFunc && func.isNative; }
bool isNormal()
{ return isState || (isFunc && func.isNormal); }
// Get the current source position (file name and line
// number). Mostly used for error messages.
Floc getFloc()
@ -92,68 +106,149 @@ struct StackPoint
int pos = code.getPos() - 1;
if(pos < 0) pos = 0;
fl.line = findLine(func.lines, pos);
if(isFunc)
fl.line = findLine(func.lines, pos);
else
fl.line = findLine(state.lines, pos);
return fl;
}
}
FunctionStack fstack;
char[] toString()
{
assert(func !is null);
char[] type, cls, name;
cls = getCls().name.str;
if(isState)
{
type = "state";
name = state.name.str;
}
else
{
assert(isFunc);
name = func.name.str;
if(isIdle) type = "idle";
else type = "function";
}
// Function location and name
return format("%s %s.%s", type, cls, name);
}
}
// 30 is somewhat small, but suitable for debugging.
StackPoint fslist[30];
alias FreeList!(StackPoint) StackList;
alias StackList.TNode *StackNode;
struct FunctionStack
{
private:
// Guard against infinite recursion
static const maxStack = 100;
// Number of native functions on the stack
int natives;
public:
StackList list;
// The current entry
StackPoint *cur = null;
// Index of next entry
int next = 0;
// Consistancy checks
invariant()
{
assert(next >= 0);
if(next > 0)
if(cur !is null)
{
assert(cur !is null);
assert(list.length > 0);
if(cur.ftype == SPType.State)
assert(next == 1);
assert(list.length == 1);
}
else assert(cur is null);
else assert(list.length == 0);
}
// Set the global stack frame pointer to correspond to the current
// entry in the fstack. Must be called when putting a thread in the
// foreground.
void restoreFrame()
{
if(cur !is null)
stack.setFrame(cur.frame);
else
stack.setFrame(null);
}
void killAll()
{
assert(natives == 0);
while(list.length)
{
assert(cur !is null);
list.remove(cur);
cur = list.getHead();
}
assert(cur is null);
}
bool hasNatives() { return natives != 0; }
// Check if the function calling us is a normal function
bool isNormal()
{
return cur !is null && cur.isNormal;
}
// Is the function stack empty?
bool isEmpty() { return next == 0; }
bool isEmpty() { return cur is null; }
bool isIdle() { return cur !is null && cur.ftype == SPType.Idle; }
// Are we currently running state code?
bool isStateCode() { return next == 1 && cur.ftype == SPType.State; }
bool isStateCode() { return list.length == 1 && cur.ftype == SPType.State; }
// Sets up the next stack point and assigns the given object
private void push(MonsterObject *obj)
{
if(next >= fslist.length)
if(list.length >= maxStack)
fail("Function stack overflow - infinite recursion?");
cur = &fslist[next++];
cur.obj = obj;
assert(cur is null || !cur.isIdle,
"Cannot call other script functions from an idle function");
// Puts a new node at the beginning of the list
cur = list.getNew();
cur.obj = obj;
cur.frame = stack.setFrame();
}
// Set the stack point up as a function. Allows obj to be null.
void push(Function *func, MonsterObject *obj)
{
//assert(cthread !is null && this is &cthread.fstack);
push(obj);
assert(func !is null);
cur.ftype = SPType.Function;
cur.func = func;
/*
writefln("Pushing ", func.name);
writefln(toString, "\n");
*/
assert(func.owner !is null);
assert(obj is null || func.owner.parentOf(obj.cls));
// Point the code stream to the byte code, if any.
if(func.isNormal)
cur.code.setData(func.bcode);
else if(func.isNative)
natives++;
assert(!func.isIdle, "don't use fstack.push() on idle functions");
}
@ -162,46 +257,74 @@ struct FunctionStack
void push(State *st, MonsterObject *obj)
{
assert(st !is null);
assert(isEmpty,
"state code can only run at the bottom of the function stack");
//writefln("Pushing state ", st.name);
push(obj);
cur.ftype = SPType.State;
cur.state = st;
assert(obj !is null);
assert(st.owner !is null);
assert(st.owner.parentOf(obj.cls));
// Set up the byte code
cur.code.setData(st.bcode);
}
// Native constructor
void pushNConst(MonsterObject *obj)
void pushIdle(Function *func, MonsterObject *obj)
{
assert(obj !is null);
push(obj);
cur.ftype = SPType.NConst;
}
void pushIdle(Function *fn, MonsterObject *obj)
{
push(obj);
cur.func = fn;
assert(func !is null);
cur.func = func;
cur.ftype = SPType.Idle;
assert(obj is null || fn.owner.parentOf(obj.cls));
assert(fn.isIdle, fn.name.str ~ "() is not an idle function");
//writefln("Pushing idle ", func.name);
assert(func.owner !is null);
assert(obj is null || func.owner.parentOf(obj.cls));
assert(func.isIdle, func.name.str ~ "() is not an idle function");
}
// Pops one entry of the stack. Checks that the stack level has been
// returned to the correct position.
void pop()
{
if(next == 0)
if(isEmpty)
fail("Function stack underflow");
stack.setFrame(cur.frame);
assert(list.length >= 1);
if(cur.isNative)
natives--;
assert(natives >= 0);
/*
if(cur.isFunc) writefln("popping function ", cur.func.name);
else if(cur.isState) writefln("popping state ", cur.state.name);
writefln(toString, "\n");
*/
// Remove the topmost node from the list, and set cur.
assert(cur == list.getHead());
list.remove(cur);
cur = list.getHead();
restoreFrame();
assert(list.length != 0 || cur is null);
}
// Get a stack trace (pretty basic at the moment)
char[] toString()
{
char[] res;
foreach(ref c; list)
res = c.toString ~ '\n' ~ res;
if(--next > 0) cur--;
else cur = null;
return "Trace:\n" ~ res;
}
}

@ -26,6 +26,18 @@ module monster.vm.idlefunction;
import monster.vm.thread;
// Idle scheduling actions - returned from initiate()
enum IS
{
Poll, // Poll the hasFinished function regularily
Return, // Return to the thread immediately - reenter() is called
// first
Kill, // Kill the thread
Manual, // Handle the scheduling ourselves. We have to schedule
// or move the thread to another ThreadList before the
// end of initiate()
}
// A callback class for idle functions. A child object of this class
// is what you "bind" to idle functions (rather than just a delegate,
// like for native functions.) Note that instances are not bound to
@ -43,7 +55,7 @@ abstract class IdleFunction
// idea. For functions which never "return", and for event driven
// idle functions (which handle their own scheduling), you should
// return false.
bool initiate(Thread*) { return true; }
abstract IS initiate(Thread*);
// This is called whenever the idle function is about to "return" to
// state code. It has to push the return value, if any, but
@ -65,5 +77,5 @@ abstract class IdleFunction
// should return false in initiate and instead reschedule the object
// manually when the event occurs. (A nice interface for this has
// not been created yet, though.)
abstract bool hasFinished(Thread*);
bool hasFinished(Thread*) { assert(0, "empty hasFinished()"); }
}

@ -36,7 +36,6 @@ import monster.compiler.enums;
import monster.vm.codestream;
import monster.vm.idlefunction;
import monster.vm.fstack;
import monster.vm.arrays;
import monster.vm.error;
import monster.vm.vm;
@ -327,6 +326,7 @@ final class MonsterClass
assert(0);
}
assert(fn !is null);
return fn;
}
@ -338,26 +338,26 @@ final class MonsterClass
void bindConst(dg_callback nf)
{
assert(constType == FuncType.Native,
assert(natConst.ftype == FuncType.Native,
"Cannot set native constructor for " ~ toString ~ ": already set");
constType = FuncType.NativeDDel;
dg_const = nf;
natConst.ftype = FuncType.NativeDDel;
natConst.natFunc_dg = nf;
}
void bindConst(fn_callback nf)
{
assert(constType == FuncType.Native,
assert(natConst.ftype == FuncType.Native,
"Cannot set native constructor for " ~ toString ~ ": already set");
constType = FuncType.NativeDFunc;
fn_const = nf;
natConst.ftype = FuncType.NativeDFunc;
natConst.natFunc_fn = nf;
}
void bindConst_c(c_callback nf)
{
assert(constType == FuncType.Native,
assert(natConst.ftype == FuncType.Native,
"Cannot set native constructor for " ~ toString ~ ": already set");
constType = FuncType.NativeCFunc;
c_const = nf;
natConst.ftype = FuncType.NativeCFunc;
natConst.natFunc_c = nf;
}
@ -572,19 +572,10 @@ final class MonsterClass
foreach(c; tree)
{
// Custom native constructor
if(c.constType != FuncType.Native)
{
fstack.pushNConst(obj);
if(c.constType == FuncType.NativeDDel)
c.dg_const();
else if(c.constType == FuncType.NativeDFunc)
c.fn_const();
else if(c.constType == FuncType.NativeCFunc)
c.c_const();
fstack.pop();
}
if(c.natConst.ftype != FuncType.Native)
natConst.call(obj);
// TODO: Call script-constructor here
// TODO: Call script constructors here
}
// Set the same state as the source
@ -732,16 +723,8 @@ final class MonsterClass
EnumDeclaration[] enumdecs;
ImportStatement[] imports;
// Native constructor type. Changed when the actual constructor is
// set.
FuncType constType = FuncType.Native;
union
{
dg_callback dg_const;
fn_callback fn_const;
c_callback c_const;
}
// Native constructor, if any
Function natConst;
/*******************************************************
* *
@ -957,13 +940,17 @@ final class MonsterClass
parse(tokens, fname);
}
// Parses a list of tokens
// Parses a list of tokens, and do other setup.
void parse(ref TokenArray tokens, char[] fname)
{
assert(!isParsed(), "parse() called on a parsed class " ~ name.str);
alias Block.isNext isNext;
natConst.ftype = FuncType.Native;
natConst.name.str = "native constructor";
natConst.owner = this;
// TODO: Check for a list of keywords here. class, module,
// abstract, final. They can come in any order, but only certain
// combinations are legal. For example, class and module cannot

@ -234,16 +234,6 @@ struct MonsterObject
return &data[treeIndex][pos];
}
/* KILLME
// Get a long (two ints) from the data segment
long *getDataLong(int pos)
{
if(pos < 0 || pos+1>=data.length)
fail("MonsterObject: data pointer out of range: " ~ toString(pos));
return cast(long*)&data[pos];
}
*/
// Get an array from the data segment
int[] getDataArray(int treeIndex, int pos, int len)
{
@ -317,19 +307,13 @@ struct MonsterObject
// Do we already have a thread?
if(sthread !is null)
{
// If we are already scheduled (if an idle function has
// scheduled us, or if setState has been called multiple
// times), unschedule. This will automatically cancel any
// scheduled idle functions and call their abort() functions.
if(sthread.isScheduled)
sthread.cancel();
assert(sthread.isActive || !sthread.stateChange,
"stateChange was set outside active code");
// If we are running from state code, signal it that we must
// now abort execution when we reach the state level.
sthread.stateChange = sthread.isActive;
// Check if the thread has gone and died on us while we were
// away.
if(sthread.isDead)
sthread = null;
else
// Still alive. Stop any execution of the thread
sthread.stop();
}
// If we are jumping to anything but the empty state, we will have
@ -349,18 +333,24 @@ struct MonsterObject
{
// Make sure there's a thread to run in
if(sthread is null)
sthread = Thread.getNew(this);
sthread = Thread.getNew();
// Schedule the thread to start at the given label
sthread.schedule(label.offs);
// Schedule the thread to start at the given state and
// label
sthread.scheduleState(this, label.offs);
assert(sthread.isScheduled);
}
}
// Don't leave an unused thread dangling - kill it instead.
if(sthread !is null && sthread.isUnused)
// If nothing is scheduled, kill the thread
if(sthread !is null && !sthread.isScheduled)
{
assert(sthread.isTransient);
sthread.kill();
// Zero out any pointers to the thread.
if(sthread is cthread)
cthread = null;
sthread = null;
}

@ -24,7 +24,7 @@
module monster.vm.params;
import monster.vm.mobject;
import monster.vm.fstack;
import monster.vm.thread;
/* This module offers a "friendly" interface for dealing with
parameters and return values on the stack. It is meant to be an
@ -44,8 +44,9 @@ struct Params
// function)
MonsterObject *obj()
{
assert(fstack.cur !is null);
assert(fstack.cur.obj !is null);
return fstack.cur.obj;
assert(cthread !is null);
assert(cthread.fstack.cur !is null);
assert(cthread.fstack.cur.obj !is null);
return cthread.fstack.cur.obj;
}
}

@ -33,7 +33,6 @@ import monster.compiler.scopes;
import monster.vm.mobject;
import monster.vm.mclass;
import monster.vm.arrays;
import monster.vm.fstack;
import monster.vm.error;
// Stack. There's only one global instance, but threads will make
@ -74,21 +73,19 @@ struct CodeStack
return total-left;
}
// Sets the current position as the 'frame pointer', and return
// previous value.
// Sets the current position as the 'frame pointer', and return it.
int *setFrame()
{
auto old = frame;
frame = pos;
fleft = left;
//writefln("setFrame(): new=%s, old=%s", frame, old);
return old;
return frame;
}
// Sets the given frame pointer
void setFrame(int *frm)
{
//writefln("setFrame(%s)", frm);
//writefln("restoring frame: %s", frm);
frame = frm;
if(frm is null)
{
@ -99,18 +96,14 @@ struct CodeStack
assert(fleft >= 0 && fleft <= total);
}
// Reset the stack level to zero. Should only be called when the
// frame pointer is already zero (we use it in state code only.)
// Reset the stack level to zero.
void reset()
{
left = total;
pos = data.ptr;
if(fleft != 0)
writefln("left=%s total=%s fleft=%s", left, total, fleft);
assert(frame is null);
assert(fleft == 0);
assert(fstack.isEmpty);
fleft = 0;
frame = null;
}
void pushInt(int i)
@ -145,6 +138,15 @@ struct CodeStack
return *(cast(long*)pos);
}
// Get the pointer from the start of the stack
int *getStartInt(int pos)
{
if(pos < 0 || pos >= getPos)
fail("CodeStack.getStartInt() pointer out of range");
return &data[pos];
}
// Get the pointer to an int at the given position backwards from
// the current stack pointer. 0 means the first int, ie. the one we
// would get if we called popInt. 1 is the next, etc

@ -78,16 +78,16 @@ struct Thread
// temporary data off the stack.
SharedType idleData;
// The contents of idleObj's extra data for the idle's owner class.
// The contents of the idle object's extra data for the idle's owner
// class.
SharedType extraData;
// Set to true whenever we are running from state code. If we are
// inside the state itself, this will be true and 'next' will be 1.
bool isActive;
// Set to true when a state change is in progress. Only used when
// state is changed from within a function in active code.
bool stateChange;
bool shouldExit;
// Function stack for this thread
FunctionStack fstack;
/*******************************************************
* *
@ -96,21 +96,11 @@ struct Thread
*******************************************************/
private:
// Temporarily needed since we need a state and an object to push on
// the stack to return to state code. This'll change soon (we won't
// need to push anything to reenter, since the function stack will
// already be set up for us.)
MonsterObject * theObj;
Function *idle;
MonsterObject *idleObj; // Object owning the idle function
NodeList * list; // List owning this thread
int retPos; // Return position in byte code.
NodeList *list; // List owning this thread
// Stored copy of the stack. Used when the thread is not running.
int[] sstack;
public:
/*******************************************************
* *
@ -118,91 +108,120 @@ struct Thread
* *
*******************************************************/
// Get a new thread. It starts in the 'unused' list.
static Thread* getNew(MonsterObject *obj = null)
// Get a new thread. It starts in the 'transient' list.
static Thread* getNew()
{
auto cn = scheduler.unused.getNew();
cn.list = &scheduler.unused;
auto cn = scheduler.transient.getNew();
cn.list = &scheduler.transient;
with(*cn)
{
theObj = obj;
// Initialize other variables
idle = null;
idleObj = null;
isActive = false;
stateChange = false;
retPos = -1;
shouldExit = false;
sstack = null;
}
/*
if(obj !is null)
writefln("Got a new state thread");
else
writefln("Got a new non-state thread");
*/
return cn;
}
// Unschedule this node from the runlist or waitlist it belongs to,
// but don't kill it. Any idle function connected to this node is
// aborted.
void cancel()
// Stop the thread and return it to the freelist
void kill()
{
if(idle !is null)
stop();
assert(fstack.isEmpty);
list.remove(this);
list = null;
assert(isDead);
}
// Stop the execution of a thread and cancel any scheduling.
void stop()
{
assert(!isDead);
// TODO: We also have to handle (forbid) cases where we are
// the caller of another thread.
if(isRunning)
{
fstack.pushIdle(idle, idleObj);
idle.idleFunc.abort(this);
fstack.pop();
idle = null;
// We are running.
assert(sstack.length == 0);
// Forbid stopping the thread if there are native functions on
// the function stack.
if(fstack.hasNatives)
fail("Cannot stop thread, there are native functions on the stack.");
// Kill the stack tell execute() to stop running
stack.reset();
shouldExit = true;
}
else
{
// We are not running
// Free the stack buffers
if(sstack.length)
Buffers.free(sstack);
sstack = null;
// Abort any idle function
if(fstack.isIdle)
{
// Abort the idle function and pop it
getIdle().abort(this);
fstack.pop();
}
assert(!fstack.hasNatives);
}
retPos = -1;
moveTo(&scheduler.unused);
// Kill the function stack
fstack.killAll();
// Move to the transient list (signalling that the thread is
// unused.)
moveTo(&scheduler.transient);
assert(!isScheduled);
}
// Remove the thread comletely
void kill()
// Schedule this thread to run state code the next frame
void scheduleState(MonsterObject *obj, int offs)
{
/*
if(theObj is null)
writefln("Killing non-state thread");
else
writefln("Killing state thread");
*/
assert(!isDead);
assert(!isScheduled,
"cannot schedule an already scheduled thread");
assert(!fstack.isIdle);
assert(fstack.isEmpty);
assert(offs >= 0);
assert(obj !is null);
cancel();
list.remove(this);
list = null;
assert(isRunning == shouldExit);
if(sstack.length)
Buffers.free(sstack);
sstack = null;
/*
writefln("Thread lists:");
writefln(" run: ", scheduler.run.length);
writefln(" runNext: ", scheduler.runNext.length);
writefln(" wait: ", scheduler.wait.length);
writefln(" unused: ", scheduler.unused.length);
*/
}
// Move to the runlist
moveTo(scheduler.runNext);
bool isDead() { return list is null; }
// Set up the function stack
fstack.push(obj.state, obj);
fstack.cur.code.jump(offs);
}
// Schedule this thread to run next frame
void schedule(int offs)
// Push a function and pause the thread
void pushFunc(Function *fn, MonsterObject *obj)
{
assert(!isDead);
assert(!isScheduled,
"cannot schedule an already scheduled thread");
assert(fstack.isEmpty);
assert(!fstack.isIdle);
assert(fn !is null);
assert(obj !is null);
assert(!fn.isIdle);
retPos = offs;
assert(offs >= 0);
moveTo(scheduler.runNext);
// Set up the function stack
assert(fn.owner.parentOf(obj));
fstack.push(fn, obj);
moveTo(&scheduler.paused);
}
// Are we currently scheduled?
@ -211,17 +230,15 @@ struct Thread
// The node is per definition scheduled if it is in one of these
// lists
return
list is &scheduler.wait ||
list is scheduler.run ||
list is scheduler.runNext;
!isDead && (list is &scheduler.wait ||
list is scheduler.run ||
list is scheduler.runNext);
}
bool isUnused()
{
return list is &scheduler.unused;
}
bool isIdle() { return idle !is null; }
bool isTransient() { return list is &scheduler.transient; }
bool isRunning() { return cthread is this; }
bool isDead() { return list is null; }
bool isPaused() { return list is &scheduler.paused; }
// Get the next node in the freelist
Thread* getNext()
@ -237,120 +254,122 @@ struct Thread
// Reenter this thread to the point where it was previously stopped.
void reenter()
{
assert(theObj !is null,
"cannot reenter a non-state thread yet");
// Most if not all of these checks will have to be removed in the
// future
assert(theObj.state !is null, "attempted to call the empty state");
assert(!isActive,
"reenter cannot be called when object is already active");
assert(fstack.isEmpty,
"can only reenter at the bottom of the function stack");
assert(isScheduled);
if(isIdle)
assert(!isDead);
assert(cthread is null,
"cannot reenter when another thread is running");
assert(!isRunning,
"reenter cannot be called when thread is already running");
assert(isScheduled || isPaused);
assert(!fstack.isEmpty);
// Put the thread in the foreground
foreground();
assert(isRunning);
// Notify the idle function, if any
if(fstack.isIdle)
{
assert(idle !is null);
assert(idleObj !is null || idle.isStatic);
// Tell the idle function that we we are reentering
fstack.pushIdle(idle, idleObj);
idle.idleFunc.reentry(this);
fstack.pop();
assert(fstack.isIdle);
getIdle().reentry(this);
// We're no longer idle
idle = null;
// Remove the idle function
fstack.pop();
}
// Set the active flat to indicate that we are now actively
// running. (Might not be needed in the future)
isActive = true;
// Set the thread
assert(cthread is null);
cthread = this;
assert(fstack.cur.isNormal,
"can only reenter script code");
// Remove the current thread from the run list
moveTo(&scheduler.unused);
// Restore the stack
restoreStack();
// Set up the code stack for state code.
fstack.push(theObj.state, theObj);
// Set the position
assert(retPos >= 0);
fstack.cur.code.jump(retPos);
moveTo(&scheduler.transient);
// Run the code
execute();
// Reset the thread
cthread = null;
fstack.pop();
// Exit immediately if the thread committed suicide.
if(isDead) return;
// We are no longer active
isActive = false;
shouldExit = false;
assert(stack.getPos == 0,
format("Stack not returned to zero after state code, __STACK__=",
stack.getPos));
if(!isUnused)
// Store the stack
acquireStack();
else
// If the thread is not used for anything, might as well kill it
kill();
// Background the thread
background();
}
// Make a copy of the stack and store it for later. Reset the global
// stack.
void acquireStack()
// Put this thread in the background. Acquires the stack and
// function stack.
void background()
{
assert(!isUnused(),
"unused threads should never need to aquire the stack");
assert(!isDead);
assert(sstack.length == 0,
"Thread already has a stack");
assert(fstack.isEmpty);
assert(isRunning,
"cannot put a non-running thread in the background");
assert(!fstack.hasNatives);
// We're no longer the current thread
cthread = null;
// This can be optimized later
int len = stack.getPos();
if(len)
if(isTransient)
{
writefln("acquiring %s ints", len);
// The thread is not scheduled and will not be used
// anymore. Might as well kill it.
assert(!isRunning);
kill();
}
else
{
// The thread will possibly be restored at some point. Store
// the stack and fstack for later.
// Get a new buffer, and copy the stack
sstack = Buffers.getInt(len);
sstack[] = stack.popInts(len);
// Stack.
int len = stack.getPos();
if(len)
{
// Get a new buffer, and copy the stack
sstack = Buffers.getInt(len);
sstack[] = stack.popInts(len);
}
}
// Clear out our stack values
stack.reset();
}
private:
/*******************************************************
* *
* Private helper functions *
* *
*******************************************************/
assert(!isRunning);
}
void restoreStack()
// Put the thread in the foreground. Restore any stored stack
// values.
void foreground()
{
assert(stack.getPos() == 0,
"cannot restore into a non-empty stack");
assert(!isDead);
assert(cthread is null,
"cannot restore thread, another thread is running");
assert(!isRunning,
"cannot restore thread, it is already running");
assert((isTransient && fstack.isEmpty) ||
stack.getPos() == 0,
"only empty transient threads kan restore into a non-empty stack");
if(sstack.length)
{
assert(stack.getPos() == 0,
"cannot restore into a non-empty stack");
assert(!isTransient,
"cannot restore a transent thread with stack");
// Push the values back, and free the buffer
stack.pushInts(sstack);
Buffers.free(sstack);
assert(stack.getPos == sstack.length);
sstack = null;
}
// Restore the stack frame pointer
fstack.restoreFrame();
// Set ourselves as the running thread
cthread = this;
}
// Move this node to another list.
@ -361,6 +380,21 @@ struct Thread
list = to;
}
private:
/*******************************************************
* *
* Private helper functions *
* *
*******************************************************/
IdleFunction getIdle()
{
assert(fstack.isIdle);
assert(fstack.cur.func !is null);
assert(fstack.cur.func.idleFunc !is null);
return fstack.cur.func.idleFunc;
}
void fail(char[] msg)
{
Floc fl;
@ -371,61 +405,72 @@ struct Thread
}
// Parse the BC.CallIdle instruction parameters and schedule the
// given idle function.
void callIdle(MonsterObject *iObj)
// given idle function. Return true if we should exit execute()
bool callIdle(MonsterObject *iObj)
{
assert(isActive && fstack.isStateCode,
"Byte code attempted to call an idle function outside of state code."); assert(!isScheduled, "Thread is already scheduled");
assert(isRunning);
assert(!isScheduled, "Thread is already scheduled");
assert(iObj !is null);
CodeStream *code = &fstack.cur.code;
if(fstack.hasNatives)
fail("Cannot run idle function: there are native functions on the stack");
// Store the object
idleObj = iObj;
assert(idleObj !is null);
CodeStream *code = &fstack.cur.code;
// Get the class from the index
auto cls = iObj.cls.upcast(code.getInt());
// Get the function
idle = cls.findFunction(code.getInt());
Function *idle = cls.findFunction(code.getInt());
assert(idle !is null && idle.isIdle);
assert(cls is idle.owner);
assert(idleObj.cls.childOf(cls));
assert(iObj.cls.childOf(cls));
// The IdleFunction object bound to this function is stored in
// idle.idleFunc
if(idle.idleFunc is null)
fail("Called unimplemented idle function '" ~ idle.name.str ~ "'");
// Set the return position
retPos = fstack.cur.code.getPos();
// Set up extraData
extraData = *idleObj.getExtra(idle.owner);
extraData = *iObj.getExtra(idle.owner);
// Notify the idle function
fstack.pushIdle(idle, idleObj);
if(idle.idleFunc.initiate(this))
moveTo(&scheduler.wait);
fstack.pop();
}
// Push the idle function on the stack, with iObj as the 'this'
// object.
fstack.pushIdle(idle, iObj);
bool shouldExit()
{
if(fstack.isStateCode && stateChange)
// Notify the idle function that it was called
auto res = idle.idleFunc.initiate(this);
//writefln("Called %s, result was %s", idle.name, res);
if(res == IS.Poll)
{
assert(isActive);
moveTo(&scheduler.wait);
return true;
}
// The state was changed while in state code. Abort the code.
stateChange = false;
if(res == IS.Return)
{
// If we're returning, call reenter immediately
idle.idleFunc.reentry(this);
// There might be dangling stack values
stack.reset();
return true;
// The function is done, pop it back of the stack
fstack.pop();
// 'false' means continue running
return false;
}
return false;
}
assert(res == IS.Manual || res == IS.Kill);
// The only difference between Manual and Kill is what list the
// thread ends in. If the thread is in the transient list, it will
// be killed automatically when it's no longer running.
assert( (res == IS.Kill) == isTransient,
res == IS.Manual ? "Manually scheduled threads must be moved to another list." : "Killed threads cannot be moved to another list.");
// 'true' means exit execute()
return true;
}
/*******************************************************
* *
@ -444,10 +489,12 @@ struct Thread
// an infinite loop.
static const long limit = 10000000;
assert(!isDead);
assert(fstack.cur !is null,
"Thread.execute called but there is no code on the function stack.");
assert(cthread == this,
assert(fstack.cur.isNormal,
"execute() can only run script code");
assert(isRunning,
"can only run the current thread");
// Get some values from the function stack
@ -544,78 +591,97 @@ struct Thread
{
case BC.Exit:
// Exit execute(). The function stack cleanup is handled
// by Our Glorious Caller.
return;
// Step down once on the function stack
fstack.pop();
case BC.Call:
{
// Get the correct function from the virtual table
val = code.getInt(); // Class index
auto fn = obj.cls.findVirtualFunc(val, code.getInt());
if(!fstack.isNormal())
// The current function isn't a script function, so
// exit.
return;
// Finally, call
fn.call(obj);
assert(!shouldExit);
if(shouldExit()) return;
break;
}
// Set up the variables and continue running.
assert(fstack.cur !is null);
cls = fstack.cur.getCls();
code = &fstack.cur.code;
obj = fstack.cur.obj;
break;
case BC.CallFar:
// Start a block so these variables are local
{
auto mo = stack.popObject();
MonsterObject *mo;
Function *fn;
case BC.Call:
mo = obj;
goto CallCommon;
case BC.CallFar:
mo = stack.popObject();
CallCommon:
// Get the correct function from the virtual table
val = code.getInt(); // Class index
auto fn = mo.cls.findVirtualFunc(val, code.getInt());
// Call the function
fn.call(mo);
fn = mo.cls.findVirtualFunc(val, code.getInt());
// Exit state code if the state was changed
if(shouldExit()) return;
if(fn.isNormal)
{
// Normal (script) function. We don't need to exit
// execute(), just change the function stack and the
// cls and code pointers. Then keep running.
fstack.push(fn, mo);
cls = fstack.cur.getCls();
code = &fstack.cur.code;
obj = mo;
assert(obj is fstack.cur.obj);
}
else
{
// Native function. Let Function handle it.
assert(fn.isNative);
fn.call(mo);
if(shouldExit) return;
}
break;
}
case BC.CallIdle:
callIdle(obj);
return;
if(callIdle(obj))
return;
break;
case BC.CallIdleFar:
callIdle(stack.popObject());
return;
if(callIdle(stack.popObject()))
return;
break;
case BC.Return:
// Remove the given number of bytes from the stack, and
// exit the function.
stack.pop(code.getInt());
return;
goto case BC.Exit;
case BC.ReturnVal:
stack.pop(code.getInt(), 1);
return;
goto case BC.Exit;
case BC.ReturnValN:
val = code.getInt(); // Get the value first, since order
// of evaluation is important.
stack.pop(val, code.getInt());
return;
goto case BC.Exit;
case BC.State:
val = code.getInt(); // State index
val2 = code.getInt(); // Label index
// Get the class index and let setState handle everything
obj.setState(val, val2, code.getInt());
if(shouldExit()) return;
if(shouldExit) return;
break;
case BC.Halt:
// A halt statement was encountered, and we should stop
// execution. This is only allowed in state code.
assert(isActive && fstack.isStateCode,
"halt command encountered outside state code.");
return;
case BC.New:
// Create a new object. Look up the class index in the
// global class table, and create an object from it.
@ -1372,9 +1438,14 @@ struct Scheduler
// Waiting list - idle threads that are actively checked each frame.
NodeList wait;
// List of unused nodes. Any thread in this list (that is not
// actively running) can and will be deleted eventually.
NodeList unused;
// List of transient nodes. Any thread in this list (that is not
// actively running) can and will be deleted when it goes into the
// background.
NodeList transient;
// List of threads that are not running or scheduled, but should not
// be deleted.
NodeList paused;
// The run lists for this and the next round. We use pointers to the
// actual lists, since we want to swap them easily.
@ -1413,6 +1484,8 @@ struct Scheduler
// good.) But all this falls in the "optimization" category.
void doFrame()
{
assert(cthread is null,
"cannot run doFrame while another thread is running");
checkConditions();
dispatch();
}
@ -1436,15 +1509,12 @@ struct Scheduler
// way, ie to change object states or interact with the
// scheduler. In fact, hasFinished() should do as little as
// possible.
if(cn.isIdle)
if(cn.fstack.isIdle)
{
fstack.pushIdle(cn.idle, cn.idleObj);
if(cn.idle.idleFunc.hasFinished(cn))
if(cn.getIdle().hasFinished(cn))
// Schedule the code to start running again this round. We
// move it from the wait list to the run list.
cn.moveTo(runNext);
fstack.pop();
}
// Set the next item
cn = next;
@ -1473,9 +1543,6 @@ struct Scheduler
// Execute
cn.reenter();
// The function stack should now be at zero
assert(fstack.isEmpty());
// Get the next item.
cn = run.getHead();
}

@ -24,7 +24,6 @@
module monster.vm.vm;
import monster.vm.error;
import monster.vm.fstack;
import monster.vm.thread;
import monster.vm.mclass;
import monster.vm.mobject;
@ -47,18 +46,21 @@ struct VM
{
// Run a script file in the context of the object obj. If no object
// is given, an instance of an empty class is used.
void run(char[] file, MonsterObject *obj = null)
Thread *run(char[] file, MonsterObject *obj = null)
{
Thread *trd;
auto func = new Function;
if(obj !is null)
{
auto func = Function(file, obj.cls);
func.call(obj);
*func = Function(file, obj.cls);
trd = func.call(obj);
}
else
{
auto func = Function(file);
func.call();
*func = Function(file);
trd = func.call();
}
return trd;
}
void frame(float time = 0)

@ -1,17 +1,27 @@
// Small script that prints the FPS to screen with regular intervals.
singleton FPSTicker;
import io, timer;
import io, timer, frames;
ulong lastFrame;
// Sleep one frame. This makes sure that we won't start running until
// the rendering begins. Not critically important, but it prevents the
// first printed value from being 'nan'.
fsleep(1);
ulong lastFrame = counter;
float lastTime = totalTime;
float delay = 1.5;
state tick
while(true)
{
begin:
sleep(delay);
print("fps:", (frames.counter-lastFrame) / delay);
lastFrame = frames.counter;
goto begin;
// Calculate differences since last frame
ulong fdiff = counter-lastFrame;
float tdiff = totalTime-lastTime;
print("fps:", fdiff / tdiff);
lastFrame = counter;
lastTime = totalTime;
}

@ -32,7 +32,6 @@ import core.resource : rnd;
import core.config;
import sound.music;
// Set up the base Monster classes we need in OpenMW
void initMonsterScripts()
{
@ -55,11 +54,9 @@ void initMonsterScripts()
{ stack.pushInt(rnd.randInt
(stack.popInt,stack.popInt));});
// Set up and run the fps ticker
auto mo = (new MonsterClass("FPSTicker")).getSing();
mo.setState("tick");
// Run the fps ticker
vm.run("fpsticker.mn");
// Load and run the test script
mc = new MonsterClass("Test");
mc.createObject().call("test");
// Run the test script
vm.run("test.mn");
}

@ -1,22 +1,12 @@
// A sample class
class Test;
// A short example script
import io, timer;
test()
{
state = printMessage;
}
sleep(6);
state printMessage
while(true)
{
// This state code will run as long as the object is in this state.
begin:
sleep(6);
loop:
print("Howdy from the world of Monster scripts!");
print("This script is located in mscripts/test.mn. Check it out!");
sleep(60);
goto loop;
}

@ -234,7 +234,6 @@ void main(char[][] args)
initBullet();
scope(exit) cleanupBullet();
if(cd.inCell)
{
setAmbient(cd.ambi.ambient, cd.ambi.sunlight,
@ -414,7 +413,7 @@ void main(char[][] args)
// This isn't necessary but it's here for testing purposes.
cellList.release(cd);
// Write some memory statistics
// Write some statistics
poolSize();
writefln(esmRegion);
writefln("Total objects: ", MonsterClass.getTotalObjects);

@ -38,7 +38,7 @@ import core.resource;
class Idle_waitUntilFinished : IdleFunction
{
override:
bool initiate(Thread*) { return true; }
IS initiate(Thread*) { return IS.Poll; }
bool hasFinished(Thread* trd)
{

Loading…
Cancel
Save