mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-01-19 23:23:52 +00:00
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
This commit is contained in:
parent
879cc132d5
commit
656007cf01
27 changed files with 1006 additions and 541 deletions
|
@ -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;
|
// We have to do some trickery to retain the stack in
|
||||||
|
// cases where the function exits completely.
|
||||||
assert(fstack.isEmpty);
|
cthread = null; // This will prevent kill() from clearing
|
||||||
|
// the stack.
|
||||||
|
ct.kill();
|
||||||
}
|
}
|
||||||
// I think we could also check fstack if it's empty instead of
|
else
|
||||||
// using wasNew. Leave these checks here to see if this assumtion
|
cthread.background();
|
||||||
// is correct.
|
}
|
||||||
else assert(!fstack.isEmpty);
|
return ct;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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();
|
||||||
|
|
9
monster/modules/frames.mn
Normal file
9
monster/modules/frames.mn
Normal file
|
@ -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);
|
42
monster/modules/thread.mn
Normal file
42
monster/modules/thread.mn
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
"; //"
|
"; //"
|
||||||
|
|
||||||
|
MonsterObject *trdSing;
|
||||||
|
|
||||||
|
class Kill : IdleFunction
|
||||||
|
{
|
||||||
|
IS initiate(Thread *t)
|
||||||
|
{
|
||||||
|
auto mo = params.obj;
|
||||||
|
|
||||||
|
if(mo !is trdSing)
|
||||||
|
{
|
||||||
|
// Check if this is another thread
|
||||||
|
auto trd = getOwner(mo);
|
||||||
|
if(trd !is t)
|
||||||
|
{
|
||||||
|
// It is. Kill it explicitly and return.
|
||||||
|
trd.kill();
|
||||||
|
return IS.Return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it's our own thread, tell the scheduler to kill it from
|
||||||
|
// the inside.
|
||||||
|
return IS.Kill;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Pause : IdleFunction
|
||||||
|
{
|
||||||
|
IS initiate(Thread *t)
|
||||||
|
{
|
||||||
|
auto mo = params.obj;
|
||||||
|
|
||||||
|
// Can only run on the singleton object
|
||||||
|
if(mo !is trdSing)
|
||||||
|
fail("Can only pause our own thread");
|
||||||
|
|
||||||
|
// Move the thread to the 'paused' list
|
||||||
|
t.moveTo(&scheduler.paused);
|
||||||
|
|
||||||
|
return IS.Manual;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Thread *getOwner() { return getOwner(params.obj); }
|
||||||
|
Thread *getOwner(MonsterObject *mo)
|
||||||
|
{
|
||||||
|
assert(mo !is null);
|
||||||
|
if(mo is trdSing)
|
||||||
|
fail("Cannot run this function on the global singleton thread.");
|
||||||
|
auto trd = cast(Thread*) mo.getExtra(_threadClass).vptr;
|
||||||
|
assert(trd !is null);
|
||||||
|
assert(!trd.isRunning || !trd.isPaused,
|
||||||
|
"thread cannot be running and paused at the same time");
|
||||||
|
return trd;
|
||||||
|
}
|
||||||
|
|
||||||
|
MonsterObject *createObj(Thread *trd)
|
||||||
|
{
|
||||||
|
assert(trd !is null);
|
||||||
|
auto mo = _threadClass.createObject();
|
||||||
|
mo.getExtra(_threadClass).vptr = trd;
|
||||||
|
return mo;
|
||||||
|
}
|
||||||
|
|
||||||
|
void create()
|
||||||
|
{
|
||||||
|
// Can only run on the singleton object
|
||||||
|
if(params.obj !is trdSing)
|
||||||
|
fail("Can only use create() on the global thread object.");
|
||||||
|
|
||||||
|
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(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()
|
void initThreadModule()
|
||||||
{
|
{
|
||||||
static MonsterClass mc;
|
if(_threadClass !is null)
|
||||||
|
|
||||||
if(mc !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; }
|
||||||
|
|
||||||
|
// Get the next element in the list
|
||||||
|
T* getNext(T* nd)
|
||||||
|
{
|
||||||
|
auto node = cast(TNode*)nd;
|
||||||
|
return cast(T*) node.getNext();
|
||||||
|
}
|
||||||
|
|
||||||
// Loop through the structs in this list
|
// Loop through the structs in this list
|
||||||
int opApply(int delegate(ref T) dg)
|
int opApply(int delegate(ref T) dg)
|
||||||
{
|
{ return nodes.opApply(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;
|
||||||
|
|
||||||
|
if(isFunc)
|
||||||
fl.line = findLine(func.lines, pos);
|
fl.line = findLine(func.lines, pos);
|
||||||
|
else
|
||||||
|
fl.line = findLine(state.lines, pos);
|
||||||
|
|
||||||
return fl;
|
return fl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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";
|
||||||
}
|
}
|
||||||
|
|
||||||
FunctionStack fstack;
|
// 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(list.length > 0);
|
||||||
|
if(cur.ftype == SPType.State)
|
||||||
|
assert(list.length == 1);
|
||||||
|
}
|
||||||
|
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);
|
assert(cur !is null);
|
||||||
if(cur.ftype == SPType.State)
|
list.remove(cur);
|
||||||
assert(next == 1);
|
cur = list.getHead();
|
||||||
}
|
}
|
||||||
else assert(cur is null);
|
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);
|
|
||||||
cur.ftype = SPType.NConst;
|
|
||||||
}
|
|
||||||
|
|
||||||
void pushIdle(Function *fn, MonsterObject *obj)
|
|
||||||
{
|
{
|
||||||
push(obj);
|
push(obj);
|
||||||
cur.func = fn;
|
assert(func !is null);
|
||||||
|
cur.func = func;
|
||||||
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(--next > 0) cur--;
|
if(cur.isNative)
|
||||||
else cur = null;
|
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;
|
||||||
|
|
||||||
|
return "Trace:\n" ~ res;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
// 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
|
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
|
|
||||||
// aborted.
|
|
||||||
void cancel()
|
|
||||||
{
|
|
||||||
if(idle !is null)
|
|
||||||
{
|
|
||||||
fstack.pushIdle(idle, idleObj);
|
|
||||||
idle.idleFunc.abort(this);
|
|
||||||
fstack.pop();
|
|
||||||
idle = null;
|
|
||||||
}
|
|
||||||
retPos = -1;
|
|
||||||
moveTo(&scheduler.unused);
|
|
||||||
|
|
||||||
assert(!isScheduled);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the thread comletely
|
|
||||||
void kill()
|
void kill()
|
||||||
{
|
{
|
||||||
/*
|
stop();
|
||||||
if(theObj is null)
|
assert(fstack.isEmpty);
|
||||||
writefln("Killing non-state thread");
|
|
||||||
else
|
|
||||||
writefln("Killing state thread");
|
|
||||||
*/
|
|
||||||
|
|
||||||
cancel();
|
|
||||||
list.remove(this);
|
list.remove(this);
|
||||||
list = null;
|
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)
|
||||||
|
{
|
||||||
|
// 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)
|
if(sstack.length)
|
||||||
Buffers.free(sstack);
|
Buffers.free(sstack);
|
||||||
sstack = null;
|
sstack = null;
|
||||||
|
|
||||||
/*
|
// Abort any idle function
|
||||||
writefln("Thread lists:");
|
if(fstack.isIdle)
|
||||||
writefln(" run: ", scheduler.run.length);
|
{
|
||||||
writefln(" runNext: ", scheduler.runNext.length);
|
// Abort the idle function and pop it
|
||||||
writefln(" wait: ", scheduler.wait.length);
|
getIdle().abort(this);
|
||||||
writefln(" unused: ", scheduler.unused.length);
|
fstack.pop();
|
||||||
*/
|
}
|
||||||
|
assert(!fstack.hasNatives);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isDead() { return list is null; }
|
// Kill the function stack
|
||||||
|
fstack.killAll();
|
||||||
|
|
||||||
// Schedule this thread to run next frame
|
// Move to the transient list (signalling that the thread is
|
||||||
void schedule(int offs)
|
// unused.)
|
||||||
|
moveTo(&scheduler.transient);
|
||||||
|
assert(!isScheduled);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schedule this thread to run state code the next frame
|
||||||
|
void scheduleState(MonsterObject *obj, int offs)
|
||||||
{
|
{
|
||||||
|
assert(!isDead);
|
||||||
assert(!isScheduled,
|
assert(!isScheduled,
|
||||||
"cannot schedule an already scheduled thread");
|
"cannot schedule an already scheduled thread");
|
||||||
|
assert(!fstack.isIdle);
|
||||||
retPos = offs;
|
assert(fstack.isEmpty);
|
||||||
assert(offs >= 0);
|
assert(offs >= 0);
|
||||||
|
assert(obj !is null);
|
||||||
|
|
||||||
|
assert(isRunning == shouldExit);
|
||||||
|
|
||||||
|
// Move to the runlist
|
||||||
moveTo(scheduler.runNext);
|
moveTo(scheduler.runNext);
|
||||||
|
|
||||||
|
// Set up the function stack
|
||||||
|
fstack.push(obj.state, obj);
|
||||||
|
fstack.cur.code.jump(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);
|
||||||
|
|
||||||
|
// Set up the function stack
|
||||||
|
assert(fn.owner.parentOf(obj));
|
||||||
|
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,98 +254,130 @@ 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");
|
||||||
|
assert(!isRunning,
|
||||||
|
"reenter cannot be called when thread is already running");
|
||||||
|
assert(isScheduled || isPaused);
|
||||||
|
assert(!fstack.isEmpty);
|
||||||
|
|
||||||
// Most if not all of these checks will have to be removed in the
|
// Put the thread in the foreground
|
||||||
// future
|
foreground();
|
||||||
assert(theObj.state !is null, "attempted to call the empty state");
|
assert(isRunning);
|
||||||
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)
|
// 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();
|
shouldExit = false;
|
||||||
|
|
||||||
// We are no longer active
|
// Background the thread
|
||||||
isActive = false;
|
background();
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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);
|
||||||
|
|
||||||
// This can be optimized later
|
// We're no longer the current thread
|
||||||
|
cthread = null;
|
||||||
|
|
||||||
|
if(isTransient)
|
||||||
|
{
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
// Stack.
|
||||||
int len = stack.getPos();
|
int len = stack.getPos();
|
||||||
if(len)
|
if(len)
|
||||||
{
|
{
|
||||||
writefln("acquiring %s ints", len);
|
|
||||||
|
|
||||||
// Get a new buffer, and copy the stack
|
// Get a new buffer, and copy the stack
|
||||||
sstack = Buffers.getInt(len);
|
sstack = Buffers.getInt(len);
|
||||||
sstack[] = stack.popInts(len);
|
sstack[] = stack.popInts(len);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear out our stack values
|
||||||
stack.reset();
|
stack.reset();
|
||||||
|
|
||||||
|
assert(!isRunning);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put the thread in the foreground. Restore any stored stack
|
||||||
|
// values.
|
||||||
|
void foreground()
|
||||||
|
{
|
||||||
|
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.
|
||||||
|
void moveTo(NodeList *to)
|
||||||
|
{
|
||||||
|
assert(list !is null);
|
||||||
|
list.moveTo(*to, this);
|
||||||
|
list = to;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -338,27 +387,12 @@ struct Thread
|
||||||
* *
|
* *
|
||||||
*******************************************************/
|
*******************************************************/
|
||||||
|
|
||||||
void restoreStack()
|
IdleFunction getIdle()
|
||||||
{
|
{
|
||||||
assert(stack.getPos() == 0,
|
assert(fstack.isIdle);
|
||||||
"cannot restore into a non-empty stack");
|
assert(fstack.cur.func !is null);
|
||||||
|
assert(fstack.cur.func.idleFunc !is null);
|
||||||
if(sstack.length)
|
return fstack.cur.func.idleFunc;
|
||||||
{
|
|
||||||
// Push the values back, and free the buffer
|
|
||||||
stack.pushInts(sstack);
|
|
||||||
Buffers.free(sstack);
|
|
||||||
assert(stack.getPos == sstack.length);
|
|
||||||
sstack = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move this node to another list.
|
|
||||||
void moveTo(NodeList *to)
|
|
||||||
{
|
|
||||||
assert(list !is null);
|
|
||||||
list.moveTo(*to, this);
|
|
||||||
list = to;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void fail(char[] msg)
|
void fail(char[] msg)
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
if(fstack.hasNatives)
|
||||||
|
fail("Cannot run idle function: there are native functions on the stack");
|
||||||
|
|
||||||
CodeStream *code = &fstack.cur.code;
|
CodeStream *code = &fstack.cur.code;
|
||||||
|
|
||||||
// Store the object
|
|
||||||
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);
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
{
|
||||||
moveTo(&scheduler.wait);
|
moveTo(&scheduler.wait);
|
||||||
fstack.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool shouldExit()
|
|
||||||
{
|
|
||||||
if(fstack.isStateCode && stateChange)
|
|
||||||
{
|
|
||||||
assert(isActive);
|
|
||||||
|
|
||||||
// The state was changed while in state code. Abort the code.
|
|
||||||
stateChange = false;
|
|
||||||
|
|
||||||
// There might be dangling stack values
|
|
||||||
stack.reset();
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(res == IS.Return)
|
||||||
|
{
|
||||||
|
// If we're returning, call reenter immediately
|
||||||
|
idle.idleFunc.reentry(this);
|
||||||
|
|
||||||
|
// 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.
|
// 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();
|
||||||
|
|
||||||
|
if(!fstack.isNormal())
|
||||||
|
// The current function isn't a script function, so
|
||||||
|
// exit.
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case BC.Call:
|
assert(!shouldExit);
|
||||||
{
|
|
||||||
// Get the correct function from the virtual table
|
|
||||||
val = code.getInt(); // Class index
|
|
||||||
auto fn = obj.cls.findVirtualFunc(val, code.getInt());
|
|
||||||
|
|
||||||
// Finally, call
|
// Set up the variables and continue running.
|
||||||
fn.call(obj);
|
assert(fstack.cur !is null);
|
||||||
|
cls = fstack.cur.getCls();
|
||||||
|
code = &fstack.cur.code;
|
||||||
|
obj = fstack.cur.obj;
|
||||||
|
|
||||||
if(shouldExit()) return;
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
// Start a block so these variables are local
|
||||||
|
{
|
||||||
|
MonsterObject *mo;
|
||||||
|
Function *fn;
|
||||||
|
|
||||||
|
case BC.Call:
|
||||||
|
mo = obj;
|
||||||
|
goto CallCommon;
|
||||||
|
|
||||||
case BC.CallFar:
|
case BC.CallFar:
|
||||||
{
|
mo = stack.popObject();
|
||||||
auto 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
|
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);
|
fn.call(mo);
|
||||||
|
if(shouldExit) return;
|
||||||
// Exit state code if the state was changed
|
}
|
||||||
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()
|
|
||||||
{
|
|
||||||
state = printMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
state printMessage
|
|
||||||
{
|
|
||||||
// This state code will run as long as the object is in this state.
|
|
||||||
begin:
|
|
||||||
sleep(6);
|
sleep(6);
|
||||||
|
|
||||||
loop:
|
while(true)
|
||||||
|
{
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
3
openmw.d
3
openmw.d
|
@ -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…
Reference in a new issue