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
actorid
nkorslund 16 years ago
parent 879cc132d5
commit 656007cf01

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

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

@ -50,8 +50,8 @@ import monster.vm.mobject;
import monster.vm.idlefunction; import monster.vm.idlefunction;
import monster.vm.mclass; import monster.vm.mclass;
import monster.vm.error; import monster.vm.error;
import monster.vm.fstack;
import monster.vm.thread; import monster.vm.thread;
import monster.vm.stack;
import monster.vm.vm; import monster.vm.vm;
import std.stdio; import std.stdio;
@ -78,10 +78,8 @@ typedef extern(C) void function() c_callback;
struct Function struct Function
{ {
// These three variables (owner, lines and bcode) are common between MonsterClass owner; // Must be the first entry
// Function and State. They MUST be placed and ordered equally in
// both structs because we're use some unsafe pointer trickery.
MonsterClass owner;
LineSpec[] lines; // Line specifications for byte code LineSpec[] lines; // Line specifications for byte code
union union
{ {
@ -91,8 +89,8 @@ struct Function
c_callback natFunc_c; c_callback natFunc_c;
IdleFunction idleFunc; // Idle function callback IdleFunction idleFunc; // Idle function callback
} }
Token name; Token name;
Type type; // Return type Type type; // Return type
FuncType ftype; // Function type FuncType ftype; // Function type
Variable* params[]; // List of parameters Variable* params[]; // List of parameters
@ -128,48 +126,49 @@ struct Function
// this is a function that takes a variable number of arguments. // this is a function that takes a variable number of arguments.
bool isVararg() { return params.length && params[$-1].isVararg; } 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 // This is used to call the given function from native code. Note
// that this is used internally for native functions, but not for // that this is used internally for native functions, but not for
// any other type. Idle functions can NOT be called directly from // any other type. Idle functions can NOT be called directly from
// native code. // native code. Returns the thread, which is never null but might be
void call(MonsterObject *obj) // dead.
Thread *call(MonsterObject *obj)
{ {
assert(obj !is null || isStatic);
// Make sure there's a thread to use // Make sure there's a thread to use
bool wasNew;
if(cthread is null) if(cthread is null)
{ {
wasNew = true; // Get a new thread and put it in the foreground
cthread = Thread.getNew(); 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 // Push the function on the stack
fstack.push(this, obj); cthread.fstack.push(this, obj);
switch(ftype) switch(ftype)
{ {
case FuncType.NativeDDel: case FuncType.NativeDDel:
natFunc_dg(); natFunc_dg();
break; goto pop;
case FuncType.NativeDFunc: case FuncType.NativeDFunc:
natFunc_fn(); natFunc_fn();
break; goto pop;
case FuncType.NativeCFunc: case FuncType.NativeCFunc:
natFunc_c(); natFunc_c();
pop:
// Remove ourselves from the function stack
cthread.fstack.pop();
break; break;
case FuncType.Normal: case FuncType.Normal:
cthread.execute(); cthread.execute();
assert(!cthread.shouldExit,
"shouldExit should only be set for state threads");
// Execute will pop itself if necessary
break; break;
case FuncType.Native: case FuncType.Native:
fail("Called unimplemented native function " ~ toString); fail("Called unimplemented native function " ~ toString);
@ -181,41 +180,38 @@ struct Function
assert(0, "unknown FuncType for " ~ toString); assert(0, "unknown FuncType for " ~ toString);
} }
// Remove ourselves from the function stack // If we started at the bottom of the function stack, put the
fstack.pop(); // thread in the background now. This will automatically delete
// the thread if it's not used.
assert(cthread !is null); assert(cthread !is null);
auto ct = cthread;
// Reset cthread, if we created it. if(wasEmpty)
if(wasNew)
{ {
// If the thread is still in the unused list, we can safely if(cthread.fstack.isEmpty && stack.getPos != 0)
// kill it. {
if(cthread.isUnused) assert(cthread.isTransient);
cthread.kill();
else
// Otherwise, store the stack
cthread.acquireStack();
cthread = null;
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 return ct;
// using wasNew. Leave these checks here to see if this assumtion
// is correct.
else assert(!fstack.isEmpty);
} }
// Call without an object. TODO: Only allowed for functions compiled // Call without an object. TODO: Only allowed for functions compiled
// without a class using compile() below, but in the future this // without a class using compile() below, but in the future it will
// will be replaced with a static function. // be allowed for static function.
void call() Thread* call()
{ {
assert(owner is int_mc); assert(owner is int_mc);
assert(owner !is null); assert(owner !is null);
assert(int_mo !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 = // 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", fail(format("Cannot override %s, parameter types do not match",
o.toString), fn.name.loc); o.toString), fn.name.loc);
// There's no big technical reason why this shouldn't be if(o.isIdle && !fn.isIdle)
// possible, but we haven't tested it or thought about it fail("Cannot override idle function " ~ o.name.str ~
// yet. " with a non-idle function", fn.name.loc);
if(o.isIdle || fn.isIdle) if(!o.isIdle && fn.isIdle)
fail("Cannot override idle functions", fn.name.loc); fail("Cannot override normal function " ~ o.name.str ~
" with an idle function", fn.name.loc);
} }
else else
{ {
@ -805,11 +802,6 @@ class FunctionCallExpr : MemberExpression
fail("Undefined function "~name.str, name.loc); 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; type = fd.type;
assert(type !is null); assert(type !is null);
@ -933,6 +925,7 @@ class FunctionCallExpr : MemberExpression
{ {
recurse = false; recurse = false;
dotImport.evalAsm(); dotImport.evalAsm();
recurse = true;
return; return;
} }
@ -944,5 +937,8 @@ class FunctionCallExpr : MemberExpression
tasm.callIdle(fd.index, fd.owner.getTreeIndex(), isMember); tasm.callIdle(fd.index, fd.owner.getTreeIndex(), isMember);
else else
tasm.callFunc(fd.index, fd.owner.getTreeIndex(), isMember); 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_bits", "int", { tasm.push(T.mant_dig); });
inserts("mant_dig", "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 // Lets add in number of bits in the exponent as well.
inserts("exp_bits", "int", { tasm.push(8*T.sizeof-T.mant_dig); }); 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-while loop (might also support do-until loops in the future)
// do statement while (expression) // do statement while (expression)
// do : label statement while (expression) // do : label statement while (expression)
@ -1396,9 +1365,19 @@ class ReturnStatement : Statement
void resolve(Scope sc) void resolve(Scope sc)
{ {
/*
// Not allowed in state code. // Not allowed in state code.
if(sc.isStateCode) if(sc.isStateCode)
fail("return not allowed in state code", loc); 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 // Store the number of local variables we have to pop of the
// stack // stack
@ -1437,13 +1416,15 @@ class ReturnStatement : Statement
void compile() void compile()
{ {
setLine(); setLine();
if(exp !is null) if(exp !is null && fn !is null)
{ {
assert(!fn.type.isVoid); assert(!fn.type.isVoid);
exp.eval(); exp.eval();
// Return an expression
tasm.exit(locals, exp.type.getSize); tasm.exit(locals, exp.type.getSize);
} }
else else
// Return without an expression
tasm.exit(locals); tasm.exit(locals);
} }
} }
@ -1483,7 +1464,6 @@ class CodeBlock : Statement
else if(WhileStatement.canParse(toks)) b = new WhileStatement; else if(WhileStatement.canParse(toks)) b = new WhileStatement;
else if(ForStatement.canParse(toks)) b = new ForStatement; else if(ForStatement.canParse(toks)) b = new ForStatement;
else if(StateStatement.canParse(toks)) b = new StateStatement; 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(LabelStatement.canParse(toks)) b = new LabelStatement;
else if(GotoStatement.canParse(toks)) b = new GotoStatement; else if(GotoStatement.canParse(toks)) b = new GotoStatement;
else if(ContinueBreakStatement.canParse(toks)) b = new ContinueBreakStatement; else if(ContinueBreakStatement.canParse(toks)) b = new ContinueBreakStatement;
@ -1607,9 +1587,9 @@ class CodeBlock : Statement
setLine(); setLine();
// If this is the main block at the state level, we must finish // 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) if(isState)
tasm.halt(); tasm.exit();
// Local variables are forbidden in any state block, no matter // Local variables are forbidden in any state block, no matter
// at what level they are // at what level they are

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

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

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

@ -19,7 +19,7 @@ float totalTime; // Time since rendering started
ulong counter; // Number of frames since program startup ulong counter; // Number of frames since program startup
// Sleep a given number of frames // 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 // 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 class IdleFrameSleep : IdleFunction
{ {
override: override:
bool initiate(Thread* cn) IS initiate(Thread* cn)
{ {
// Calculate the return frame // Calculate the return frame
cn.idleData.l = frames + stack.popInt; cn.idleData.l = frames + stack.popInt;
// Schedule us // Schedule us
return true; return IS.Poll;
} }
bool hasFinished(Thread* cn) bool hasFinished(Thread* cn)
@ -78,7 +78,7 @@ void initFramesModule()
mc = new MonsterClass(MC.String, moduleDef, "frames"); mc = new MonsterClass(MC.String, moduleDef, "frames");
// Bind the idle // 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. // Get pointers to the variables so we can write to them easily.
auto mo = mc.getSing(); 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 // This module provides an interface to the virtual threading API in
// Monster. Not done. // Monster.
module monster.modules.threads; module monster.modules.threads;
/*
import monster.monster; import monster.monster;
import std.stdio;
const char[] moduleDef = const char[] moduleDef =
"singleton thread; "singleton thread;
native cancel();
native schedule(); // Used to kill or pause our own or other threads.
idle kill();
idle pause(); 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; 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 class IdleSleep_SystemClock : IdleFunction
{ {
override: override:
bool initiate(Thread* cn) IS initiate(Thread* cn)
{ {
// Get the parameter // Get the parameter
float secs = stack.popFloat; float secs = stack.popFloat;
@ -38,7 +38,7 @@ class IdleSleep_SystemClock : IdleFunction
cn.idleData.l += secs*TicksPerSecond; cn.idleData.l += secs*TicksPerSecond;
// Schedule us // Schedule us
return true; return IS.Poll;
} }
bool hasFinished(Thread* cn) bool hasFinished(Thread* cn)
@ -56,7 +56,7 @@ class IdleSleep_SystemClock : IdleFunction
class IdleSleep_Timer : IdleFunction class IdleSleep_Timer : IdleFunction
{ {
override: override:
bool initiate(Thread* cn) IS initiate(Thread* cn)
{ {
// The timer is stored in the object's 'extra' pointer // The timer is stored in the object's 'extra' pointer
auto t = cast(SleepManager)cn.extraData.obj; 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); cn.idleData.l = t.current + cast(long)(t.tickSize*stack.popFloat);
// Schedule us // Schedule us
return true; return IS.Poll;
} }
bool hasFinished(Thread* cn) bool hasFinished(Thread* cn)

@ -391,9 +391,14 @@ struct FreeList(T)
// Get the first element in the list // Get the first element in the list
T* getHead() { return &nodes.getHead().value; } T* getHead() { return &nodes.getHead().value; }
// Loop through the structs in this list // Get the next element in the list
int opApply(int delegate(ref T) dg) 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.mclass;
import monster.vm.stack; import monster.vm.stack;
import monster.vm.error; import monster.vm.error;
import monster.vm.thread;
import monster.compiler.states; import monster.compiler.states;
import monster.compiler.functions; import monster.compiler.functions;
import monster.compiler.linespec; import monster.compiler.linespec;
import monster.util.freelist;
import std.stdio;
import std.string;
// "friendly" parameter and stack handling. // "friendly" parameter and stack handling.
enum SPType enum SPType
{ {
Function, // A function (script or native) Function, // A function (script or native)
Idle, // Idle function Idle, // Idle function
State, // State code State, // State code
NConst, // Native constructor
} }
// One entry in the function stack // One entry in the function stack
@ -59,7 +64,7 @@ struct StackPoint
// Could have an afterStack to check that the function has the // Could have an afterStack to check that the function has the
// correct imprint (corresponding to an imprint-var in Function.) // 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 // Get the class owning the function
MonsterClass getCls() MonsterClass getCls()
@ -78,6 +83,15 @@ struct StackPoint
bool isState() bool isState()
{ return ftype == SPType.State; } { 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 // Get the current source position (file name and line
// number). Mostly used for error messages. // number). Mostly used for error messages.
Floc getFloc() Floc getFloc()
@ -92,68 +106,149 @@ struct StackPoint
int pos = code.getPos() - 1; int pos = code.getPos() - 1;
if(pos < 0) pos = 0; 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; 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. alias FreeList!(StackPoint) StackList;
StackPoint fslist[30]; alias StackList.TNode *StackNode;
struct FunctionStack 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 // The current entry
StackPoint *cur = null; StackPoint *cur = null;
// Index of next entry
int next = 0;
// Consistancy checks // Consistancy checks
invariant() invariant()
{ {
assert(next >= 0); if(cur !is null)
if(next > 0)
{ {
assert(cur !is null); assert(list.length > 0);
if(cur.ftype == SPType.State) 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? // 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? // 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 // Sets up the next stack point and assigns the given object
private void push(MonsterObject *obj) private void push(MonsterObject *obj)
{ {
if(next >= fslist.length) if(list.length >= maxStack)
fail("Function stack overflow - infinite recursion?"); fail("Function stack overflow - infinite recursion?");
cur = &fslist[next++]; assert(cur is null || !cur.isIdle,
cur.obj = obj; "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(); cur.frame = stack.setFrame();
} }
// Set the stack point up as a function. Allows obj to be null. // Set the stack point up as a function. Allows obj to be null.
void push(Function *func, MonsterObject *obj) void push(Function *func, MonsterObject *obj)
{ {
//assert(cthread !is null && this is &cthread.fstack);
push(obj); push(obj);
assert(func !is null);
cur.ftype = SPType.Function; cur.ftype = SPType.Function;
cur.func = func; cur.func = func;
/*
writefln("Pushing ", func.name);
writefln(toString, "\n");
*/
assert(func.owner !is null);
assert(obj is null || func.owner.parentOf(obj.cls)); assert(obj is null || func.owner.parentOf(obj.cls));
// Point the code stream to the byte code, if any. // Point the code stream to the byte code, if any.
if(func.isNormal) if(func.isNormal)
cur.code.setData(func.bcode); cur.code.setData(func.bcode);
else if(func.isNative)
natives++;
assert(!func.isIdle, "don't use fstack.push() on idle functions"); assert(!func.isIdle, "don't use fstack.push() on idle functions");
} }
@ -162,46 +257,74 @@ struct FunctionStack
void push(State *st, MonsterObject *obj) void push(State *st, MonsterObject *obj)
{ {
assert(st !is null); 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); push(obj);
cur.ftype = SPType.State; cur.ftype = SPType.State;
cur.state = st; cur.state = st;
assert(obj !is null); assert(obj !is null);
assert(st.owner !is null);
assert(st.owner.parentOf(obj.cls)); assert(st.owner.parentOf(obj.cls));
// Set up the byte code // Set up the byte code
cur.code.setData(st.bcode); cur.code.setData(st.bcode);
} }
// Native constructor void pushIdle(Function *func, MonsterObject *obj)
void pushNConst(MonsterObject *obj)
{ {
assert(obj !is null);
push(obj); push(obj);
cur.ftype = SPType.NConst; assert(func !is null);
} cur.func = func;
void pushIdle(Function *fn, MonsterObject *obj)
{
push(obj);
cur.func = fn;
cur.ftype = SPType.Idle; cur.ftype = SPType.Idle;
assert(obj is null || fn.owner.parentOf(obj.cls)); //writefln("Pushing idle ", func.name);
assert(fn.isIdle, fn.name.str ~ "() is not an idle function");
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 // Pops one entry of the stack. Checks that the stack level has been
// returned to the correct position. // returned to the correct position.
void pop() void pop()
{ {
if(next == 0) if(isEmpty)
fail("Function stack underflow"); 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--; return "Trace:\n" ~ res;
else cur = null;
} }
} }

@ -26,6 +26,18 @@ module monster.vm.idlefunction;
import monster.vm.thread; 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 // A callback class for idle functions. A child object of this class
// is what you "bind" to idle functions (rather than just a delegate, // is what you "bind" to idle functions (rather than just a delegate,
// like for native functions.) Note that instances are not bound to // 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 // idea. For functions which never "return", and for event driven
// idle functions (which handle their own scheduling), you should // idle functions (which handle their own scheduling), you should
// return false. // return false.
bool initiate(Thread*) { return true; } abstract IS initiate(Thread*);
// This is called whenever the idle function is about to "return" to // This is called whenever the idle function is about to "return" to
// state code. It has to push the return value, if any, but // 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 // should return false in initiate and instead reschedule the object
// manually when the event occurs. (A nice interface for this has // manually when the event occurs. (A nice interface for this has
// not been created yet, though.) // 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.codestream;
import monster.vm.idlefunction; import monster.vm.idlefunction;
import monster.vm.fstack;
import monster.vm.arrays; import monster.vm.arrays;
import monster.vm.error; import monster.vm.error;
import monster.vm.vm; import monster.vm.vm;
@ -327,6 +326,7 @@ final class MonsterClass
assert(0); assert(0);
} }
assert(fn !is null);
return fn; return fn;
} }
@ -338,26 +338,26 @@ final class MonsterClass
void bindConst(dg_callback nf) void bindConst(dg_callback nf)
{ {
assert(constType == FuncType.Native, assert(natConst.ftype == FuncType.Native,
"Cannot set native constructor for " ~ toString ~ ": already set"); "Cannot set native constructor for " ~ toString ~ ": already set");
constType = FuncType.NativeDDel; natConst.ftype = FuncType.NativeDDel;
dg_const = nf; natConst.natFunc_dg = nf;
} }
void bindConst(fn_callback nf) void bindConst(fn_callback nf)
{ {
assert(constType == FuncType.Native, assert(natConst.ftype == FuncType.Native,
"Cannot set native constructor for " ~ toString ~ ": already set"); "Cannot set native constructor for " ~ toString ~ ": already set");
constType = FuncType.NativeDFunc; natConst.ftype = FuncType.NativeDFunc;
fn_const = nf; natConst.natFunc_fn = nf;
} }
void bindConst_c(c_callback nf) void bindConst_c(c_callback nf)
{ {
assert(constType == FuncType.Native, assert(natConst.ftype == FuncType.Native,
"Cannot set native constructor for " ~ toString ~ ": already set"); "Cannot set native constructor for " ~ toString ~ ": already set");
constType = FuncType.NativeCFunc; natConst.ftype = FuncType.NativeCFunc;
c_const = nf; natConst.natFunc_c = nf;
} }
@ -572,19 +572,10 @@ final class MonsterClass
foreach(c; tree) foreach(c; tree)
{ {
// Custom native constructor // Custom native constructor
if(c.constType != FuncType.Native) if(c.natConst.ftype != FuncType.Native)
{ natConst.call(obj);
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();
}
// TODO: Call script-constructor here // TODO: Call script constructors here
} }
// Set the same state as the source // Set the same state as the source
@ -732,16 +723,8 @@ final class MonsterClass
EnumDeclaration[] enumdecs; EnumDeclaration[] enumdecs;
ImportStatement[] imports; ImportStatement[] imports;
// Native constructor type. Changed when the actual constructor is // Native constructor, if any
// set. Function natConst;
FuncType constType = FuncType.Native;
union
{
dg_callback dg_const;
fn_callback fn_const;
c_callback c_const;
}
/******************************************************* /*******************************************************
* * * *
@ -957,13 +940,17 @@ final class MonsterClass
parse(tokens, fname); parse(tokens, fname);
} }
// Parses a list of tokens // Parses a list of tokens, and do other setup.
void parse(ref TokenArray tokens, char[] fname) void parse(ref TokenArray tokens, char[] fname)
{ {
assert(!isParsed(), "parse() called on a parsed class " ~ name.str); assert(!isParsed(), "parse() called on a parsed class " ~ name.str);
alias Block.isNext isNext; 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, // TODO: Check for a list of keywords here. class, module,
// abstract, final. They can come in any order, but only certain // abstract, final. They can come in any order, but only certain
// combinations are legal. For example, class and module cannot // combinations are legal. For example, class and module cannot

@ -234,16 +234,6 @@ struct MonsterObject
return &data[treeIndex][pos]; 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 // Get an array from the data segment
int[] getDataArray(int treeIndex, int pos, int len) int[] getDataArray(int treeIndex, int pos, int len)
{ {
@ -317,19 +307,13 @@ struct MonsterObject
// Do we already have a thread? // Do we already have a thread?
if(sthread !is null) if(sthread !is null)
{ {
// If we are already scheduled (if an idle function has // Check if the thread has gone and died on us while we were
// scheduled us, or if setState has been called multiple // away.
// times), unschedule. This will automatically cancel any if(sthread.isDead)
// scheduled idle functions and call their abort() functions. sthread = null;
if(sthread.isScheduled) else
sthread.cancel(); // Still alive. Stop any execution of the thread
sthread.stop();
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;
} }
// If we are jumping to anything but the empty state, we will have // 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 // Make sure there's a thread to run in
if(sthread is null) if(sthread is null)
sthread = Thread.getNew(this); sthread = Thread.getNew();
// Schedule the thread to start at the given label // Schedule the thread to start at the given state and
sthread.schedule(label.offs); // label
sthread.scheduleState(this, label.offs);
assert(sthread.isScheduled); assert(sthread.isScheduled);
} }
} }
// Don't leave an unused thread dangling - kill it instead. // If nothing is scheduled, kill the thread
if(sthread !is null && sthread.isUnused) if(sthread !is null && !sthread.isScheduled)
{ {
assert(sthread.isTransient);
sthread.kill(); sthread.kill();
// Zero out any pointers to the thread.
if(sthread is cthread)
cthread = null;
sthread = null; sthread = null;
} }

@ -24,7 +24,7 @@
module monster.vm.params; module monster.vm.params;
import monster.vm.mobject; import monster.vm.mobject;
import monster.vm.fstack; import monster.vm.thread;
/* This module offers a "friendly" interface for dealing with /* This module offers a "friendly" interface for dealing with
parameters and return values on the stack. It is meant to be an parameters and return values on the stack. It is meant to be an
@ -44,8 +44,9 @@ struct Params
// function) // function)
MonsterObject *obj() MonsterObject *obj()
{ {
assert(fstack.cur !is null); assert(cthread !is null);
assert(fstack.cur.obj !is null); assert(cthread.fstack.cur !is null);
return fstack.cur.obj; 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.mobject;
import monster.vm.mclass; import monster.vm.mclass;
import monster.vm.arrays; import monster.vm.arrays;
import monster.vm.fstack;
import monster.vm.error; import monster.vm.error;
// Stack. There's only one global instance, but threads will make // Stack. There's only one global instance, but threads will make
@ -74,21 +73,19 @@ struct CodeStack
return total-left; return total-left;
} }
// Sets the current position as the 'frame pointer', and return // Sets the current position as the 'frame pointer', and return it.
// previous value.
int *setFrame() int *setFrame()
{ {
auto old = frame;
frame = pos; frame = pos;
fleft = left; fleft = left;
//writefln("setFrame(): new=%s, old=%s", frame, old); //writefln("setFrame(): new=%s, old=%s", frame, old);
return old; return frame;
} }
// Sets the given frame pointer // Sets the given frame pointer
void setFrame(int *frm) void setFrame(int *frm)
{ {
//writefln("setFrame(%s)", frm); //writefln("restoring frame: %s", frm);
frame = frm; frame = frm;
if(frm is null) if(frm is null)
{ {
@ -99,18 +96,14 @@ struct CodeStack
assert(fleft >= 0 && fleft <= total); assert(fleft >= 0 && fleft <= total);
} }
// Reset the stack level to zero. Should only be called when the // Reset the stack level to zero.
// frame pointer is already zero (we use it in state code only.)
void reset() void reset()
{ {
left = total; left = total;
pos = data.ptr; pos = data.ptr;
if(fleft != 0) fleft = 0;
writefln("left=%s total=%s fleft=%s", left, total, fleft); frame = null;
assert(frame is null);
assert(fleft == 0);
assert(fstack.isEmpty);
} }
void pushInt(int i) void pushInt(int i)
@ -145,6 +138,15 @@ struct CodeStack
return *(cast(long*)pos); 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 // 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 // the current stack pointer. 0 means the first int, ie. the one we
// would get if we called popInt. 1 is the next, etc // would get if we called popInt. 1 is the next, etc

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

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

@ -1,17 +1,27 @@
// Small script that prints the FPS to screen with regular intervals. // 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; float delay = 1.5;
state tick while(true)
{ {
begin:
sleep(delay); sleep(delay);
print("fps:", (frames.counter-lastFrame) / delay);
lastFrame = frames.counter; // Calculate differences since last frame
goto begin; 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 core.config;
import sound.music; import sound.music;
// Set up the base Monster classes we need in OpenMW // Set up the base Monster classes we need in OpenMW
void initMonsterScripts() void initMonsterScripts()
{ {
@ -55,11 +54,9 @@ void initMonsterScripts()
{ stack.pushInt(rnd.randInt { stack.pushInt(rnd.randInt
(stack.popInt,stack.popInt));}); (stack.popInt,stack.popInt));});
// Set up and run the fps ticker // Run the fps ticker
auto mo = (new MonsterClass("FPSTicker")).getSing(); vm.run("fpsticker.mn");
mo.setState("tick");
// Load and run the test script // Run the test script
mc = new MonsterClass("Test"); vm.run("test.mn");
mc.createObject().call("test");
} }

@ -1,22 +1,12 @@
// A sample class // A short example script
class Test;
import io, timer; import io, timer;
test() sleep(6);
{
state = printMessage;
}
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("Howdy from the world of Monster scripts!");
print("This script is located in mscripts/test.mn. Check it out!"); print("This script is located in mscripts/test.mn. Check it out!");
sleep(60); sleep(60);
goto loop;
} }

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

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

Loading…
Cancel
Save