diff --git a/monster/compiler/assembler.d b/monster/compiler/assembler.d index 0673da9e4..d4bd7ab4a 100644 --- a/monster/compiler/assembler.d +++ b/monster/compiler/assembler.d @@ -381,8 +381,6 @@ struct Assembler addi(func); } - void halt() { cmd(BC.Halt); } - void newObj(int i) { cmd(BC.New); diff --git a/monster/compiler/bytecode.d b/monster/compiler/bytecode.d index f30538377..1f7af82d6 100644 --- a/monster/compiler/bytecode.d +++ b/monster/compiler/bytecode.d @@ -68,8 +68,6 @@ enum BC // index must also be -1, and the class index // is ignored. - Halt, // Halt execution. Only allowed in state code. - New, // Create a new object. Followed by an int // giving the class index (in the file lookup // table) @@ -508,7 +506,6 @@ char[][] bcToString = BC.ReturnVal: "ReturnVal", BC.ReturnValN: "ReturnValN", BC.State: "State", - BC.Halt: "Halt", BC.New: "New", BC.Jump: "Jump", BC.JumpZ: "JumpZ", diff --git a/monster/compiler/functions.d b/monster/compiler/functions.d index 13ba4341c..4a8f315ee 100644 --- a/monster/compiler/functions.d +++ b/monster/compiler/functions.d @@ -50,8 +50,8 @@ import monster.vm.mobject; import monster.vm.idlefunction; import monster.vm.mclass; import monster.vm.error; -import monster.vm.fstack; import monster.vm.thread; +import monster.vm.stack; import monster.vm.vm; import std.stdio; @@ -78,10 +78,8 @@ typedef extern(C) void function() c_callback; struct Function { - // These three variables (owner, lines and bcode) are common between - // Function and State. They MUST be placed and ordered equally in - // both structs because we're use some unsafe pointer trickery. - MonsterClass owner; + MonsterClass owner; // Must be the first entry + LineSpec[] lines; // Line specifications for byte code union { @@ -91,8 +89,8 @@ struct Function c_callback natFunc_c; IdleFunction idleFunc; // Idle function callback } - Token name; + Type type; // Return type FuncType ftype; // Function type Variable* params[]; // List of parameters @@ -128,48 +126,49 @@ struct Function // this is a function that takes a variable number of arguments. bool isVararg() { return params.length && params[$-1].isVararg; } - // It would be cool to have a template version of call that took and - // returned the correct parameters, and could even check - // them. However, doing generic type inference is either very - // dangerous or would involve complicated checks (think basing an - // ulong or a a byte to float parameters.) The best thing to do is - // to handle types at the binding stage, allowing things like - // bind!(int,float,int)("myfunc", myfunc) - does type checking for - // you. A c++ version could be much more difficult to handle, and - // might rely more on manual coding. - // This is used to call the given function from native code. Note // that this is used internally for native functions, but not for // any other type. Idle functions can NOT be called directly from - // native code. - void call(MonsterObject *obj) + // native code. Returns the thread, which is never null but might be + // dead. + Thread *call(MonsterObject *obj) { - assert(obj !is null || isStatic); - // Make sure there's a thread to use - bool wasNew; if(cthread is null) { - wasNew = true; - cthread = Thread.getNew(); + // Get a new thread and put it in the foreground + auto tr = Thread.getNew(); + tr.foreground(); + assert(tr is cthread); } + assert(cthread !is null); + assert(!cthread.isDead); + + bool wasEmpty = cthread.fstack.isEmpty; + // Push the function on the stack - fstack.push(this, obj); + cthread.fstack.push(this, obj); switch(ftype) { case FuncType.NativeDDel: natFunc_dg(); - break; + goto pop; case FuncType.NativeDFunc: natFunc_fn(); - break; + goto pop; case FuncType.NativeCFunc: natFunc_c(); + pop: + // Remove ourselves from the function stack + cthread.fstack.pop(); break; case FuncType.Normal: cthread.execute(); + assert(!cthread.shouldExit, + "shouldExit should only be set for state threads"); + // Execute will pop itself if necessary break; case FuncType.Native: fail("Called unimplemented native function " ~ toString); @@ -181,41 +180,38 @@ struct Function assert(0, "unknown FuncType for " ~ toString); } - // Remove ourselves from the function stack - fstack.pop(); - + // If we started at the bottom of the function stack, put the + // thread in the background now. This will automatically delete + // the thread if it's not used. assert(cthread !is null); - - // Reset cthread, if we created it. - if(wasNew) + auto ct = cthread; + if(wasEmpty) { - // If the thread is still in the unused list, we can safely - // kill it. - if(cthread.isUnused) - cthread.kill(); - else - // Otherwise, store the stack - cthread.acquireStack(); - - cthread = null; + if(cthread.fstack.isEmpty && stack.getPos != 0) + { + assert(cthread.isTransient); - assert(fstack.isEmpty); + // We have to do some trickery to retain the stack in + // cases where the function exits completely. + cthread = null; // This will prevent kill() from clearing + // the stack. + ct.kill(); + } + else + cthread.background(); } - // I think we could also check fstack if it's empty instead of - // using wasNew. Leave these checks here to see if this assumtion - // is correct. - else assert(!fstack.isEmpty); + return ct; } // Call without an object. TODO: Only allowed for functions compiled - // without a class using compile() below, but in the future this - // will be replaced with a static function. - void call() + // without a class using compile() below, but in the future it will + // be allowed for static function. + Thread* call() { assert(owner is int_mc); assert(owner !is null); assert(int_mo !is null); - call(int_mo); + return call(int_mo); } // This allows you to compile a function file by writing fn = @@ -662,11 +658,12 @@ class FuncDeclaration : Statement fail(format("Cannot override %s, parameter types do not match", o.toString), fn.name.loc); - // There's no big technical reason why this shouldn't be - // possible, but we haven't tested it or thought about it - // yet. - if(o.isIdle || fn.isIdle) - fail("Cannot override idle functions", fn.name.loc); + if(o.isIdle && !fn.isIdle) + fail("Cannot override idle function " ~ o.name.str ~ + " with a non-idle function", fn.name.loc); + if(!o.isIdle && fn.isIdle) + fail("Cannot override normal function " ~ o.name.str ~ + " with an idle function", fn.name.loc); } else { @@ -805,11 +802,6 @@ class FunctionCallExpr : MemberExpression fail("Undefined function "~name.str, name.loc); } - // Is this an idle function? - if(fd.isIdle && !sc.isStateCode) - fail("Idle functions can only be called from state code", - name.loc); - type = fd.type; assert(type !is null); @@ -933,6 +925,7 @@ class FunctionCallExpr : MemberExpression { recurse = false; dotImport.evalAsm(); + recurse = true; return; } @@ -944,5 +937,8 @@ class FunctionCallExpr : MemberExpression tasm.callIdle(fd.index, fd.owner.getTreeIndex(), isMember); else tasm.callFunc(fd.index, fd.owner.getTreeIndex(), isMember); + + // Reset pdone incase the expression is evaluated again + pdone = false; } } diff --git a/monster/compiler/properties.d b/monster/compiler/properties.d index d240c2ed6..1a8d3fcf4 100644 --- a/monster/compiler/properties.d +++ b/monster/compiler/properties.d @@ -112,8 +112,8 @@ class FloatingProperties(T) : NumericProperties!(T) inserts("mant_bits", "int", { tasm.push(T.mant_dig); }); inserts("mant_dig", "int", { tasm.push(T.mant_dig); }); - // Lets add in number of bits in the exponent as well - inserts("exp_bits", "int", { tasm.push(8*T.sizeof-T.mant_dig); }); + // Lets add in number of bits in the exponent as well. + inserts("exp_bits", "int", { tasm.push(cast(uint)(8*T.sizeof-T.mant_dig)); }); } } diff --git a/monster/compiler/statement.d b/monster/compiler/statement.d index 1aee0d95f..5b5da5110 100644 --- a/monster/compiler/statement.d +++ b/monster/compiler/statement.d @@ -438,37 +438,6 @@ class ContinueBreakStatement : Statement } } -// halt; stops the execution of state code (without changing the -// state) -class HaltStatement : Statement -{ - static bool canParse(TokenArray toks) - { - return isNext(toks, TT.Halt); - } - - void parse(ref TokenArray toks) - { - if(!isNext(toks, TT.Halt, loc)) - assert(0, "Internal error"); - - if(!isNext(toks, TT.Semicolon)) - fail("halt expected ;", toks); - } - - void resolve(Scope sc) - { - if(!sc.isStateCode) - fail("Halt statements are only allowed in state code", loc); - } - - void compile() - { - setLine(); - tasm.halt(); - } -} - // do-while loop (might also support do-until loops in the future) // do statement while (expression) // do : label statement while (expression) @@ -1396,9 +1365,19 @@ class ReturnStatement : Statement void resolve(Scope sc) { + /* // Not allowed in state code. if(sc.isStateCode) fail("return not allowed in state code", loc); + */ + // Return in state code is handled as a special case + if(sc.isStateCode) + { + assert(!sc.isInFunc); + if(exp !is null) + fail("Cannot return an expression in state code", loc); + return; + } // Store the number of local variables we have to pop of the // stack @@ -1437,13 +1416,15 @@ class ReturnStatement : Statement void compile() { setLine(); - if(exp !is null) + if(exp !is null && fn !is null) { assert(!fn.type.isVoid); exp.eval(); + // Return an expression tasm.exit(locals, exp.type.getSize); } else + // Return without an expression tasm.exit(locals); } } @@ -1483,7 +1464,6 @@ class CodeBlock : Statement else if(WhileStatement.canParse(toks)) b = new WhileStatement; else if(ForStatement.canParse(toks)) b = new ForStatement; else if(StateStatement.canParse(toks)) b = new StateStatement; - else if(HaltStatement.canParse(toks)) b = new HaltStatement; else if(LabelStatement.canParse(toks)) b = new LabelStatement; else if(GotoStatement.canParse(toks)) b = new GotoStatement; else if(ContinueBreakStatement.canParse(toks)) b = new ContinueBreakStatement; @@ -1607,9 +1587,9 @@ class CodeBlock : Statement setLine(); // If this is the main block at the state level, we must finish - // it with a halt instruction. + // it with an exit instruction. if(isState) - tasm.halt(); + tasm.exit(); // Local variables are forbidden in any state block, no matter // at what level they are diff --git a/monster/compiler/states.d b/monster/compiler/states.d index 688971438..253b331be 100644 --- a/monster/compiler/states.d +++ b/monster/compiler/states.d @@ -38,13 +38,12 @@ import std.stdio; struct State { - // These three variables (owner, lines and bcode) are common between - // Function and State. They MUST be placed and ordered equally in - // both structs because we're use some unsafe pointer trickery. - MonsterClass owner; + MonsterClass owner; // This must be the first entry, since we're + // using some pointer trickery with Function and + // State. + LineSpec[] lines; // Line specifications for byte code ubyte[] bcode; // Final compiled code - Token name; int index; diff --git a/monster/compiler/tokenizer.d b/monster/compiler/tokenizer.d index b798eaee6..f66ba8b2a 100644 --- a/monster/compiler/tokenizer.d +++ b/monster/compiler/tokenizer.d @@ -100,11 +100,11 @@ enum TT Return, Switch, Select, State, - Struct, Enum, Thread, + Struct, Enum, Import, Clone, Override, Final, Function, With, This, New, Static, Const, Out, Ref, Abstract, Idle, Public, Private, Protected, True, False, Native, Null, - Goto, Halt, Var, In, + Goto, Var, In, Last, // Tokens after this do not have a specific string // associated with them. @@ -224,7 +224,6 @@ const char[][] tokenList = TT.State : "state", TT.Struct : "struct", TT.Enum : "enum", - TT.Thread : "thread", TT.Import : "import", TT.Typeof : "typeof", TT.Singleton : "singleton", @@ -247,7 +246,6 @@ const char[][] tokenList = TT.Native : "native", TT.Null : "null", TT.Goto : "goto", - TT.Halt : "halt", TT.Var : "var", TT.In : "in", diff --git a/monster/modules/all.d b/monster/modules/all.d index 52484e3f8..4f364f410 100644 --- a/monster/modules/all.d +++ b/monster/modules/all.d @@ -3,10 +3,12 @@ module monster.modules.all; import monster.modules.io; import monster.modules.timer; import monster.modules.frames; +import monster.modules.threads; void initAllModules() { initIOModule(); initTimerModule(); initFramesModule(); + initThreadModule; } diff --git a/monster/modules/frames.d b/monster/modules/frames.d index 94b8a9abb..353a574e9 100644 --- a/monster/modules/frames.d +++ b/monster/modules/frames.d @@ -19,7 +19,7 @@ float totalTime; // Time since rendering started ulong counter; // Number of frames since program startup // Sleep a given number of frames -idle sleep(int frameNum); +idle fsleep(int frameNum); "; //" // Keep local copies of these, since we don't want Monster code to @@ -54,13 +54,13 @@ void updateFrames(float time, int frmCount = 1) class IdleFrameSleep : IdleFunction { override: - bool initiate(Thread* cn) + IS initiate(Thread* cn) { // Calculate the return frame cn.idleData.l = frames + stack.popInt; // Schedule us - return true; + return IS.Poll; } bool hasFinished(Thread* cn) @@ -78,7 +78,7 @@ void initFramesModule() mc = new MonsterClass(MC.String, moduleDef, "frames"); // Bind the idle - mc.bind("sleep", new IdleFrameSleep); + mc.bind("fsleep", new IdleFrameSleep); // Get pointers to the variables so we can write to them easily. auto mo = mc.getSing(); diff --git a/monster/modules/frames.mn b/monster/modules/frames.mn new file mode 100644 index 000000000..5fc5889a8 --- /dev/null +++ b/monster/modules/frames.mn @@ -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); diff --git a/monster/modules/thread.mn b/monster/modules/thread.mn new file mode 100644 index 000000000..e1e8aa004 --- /dev/null +++ b/monster/modules/thread.mn @@ -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; +} diff --git a/monster/modules/threads.d b/monster/modules/threads.d index ea3074ec5..cf589c156 100644 --- a/monster/modules/threads.d +++ b/monster/modules/threads.d @@ -1,25 +1,284 @@ // This module provides an interface to the virtual threading API in -// Monster. Not done. +// Monster. module monster.modules.threads; -/* import monster.monster; +import std.stdio; const char[] moduleDef = "singleton thread; -native cancel(); -native schedule(); + +// Used to kill or pause our own or other threads. +idle kill(); idle pause(); + +// Get status information about a thread +native bool isScheduled(); +native bool isPaused(); +native bool isIdle(); +native bool isDead(); +bool isAlive() { return !isDead(); } + +// Create a new (paused) thread for a given function +native thread create(char[] name); + +// Schedule a (paused) thread to run the next frame +native restart(); + +// Call a (paused) thread directly - returns when the thread exits or +// calls an idle function. +idle resume(); + +// Wait for a thread to finish. Will not return until the thread is +// dead. +idle wait(); + +// Call a function as a thread +thread call(char[] name) +{ + var t = create(name); + t.resume(); + return t; +} + +// Start a function as a thread in the background +thread start(char[] name) +{ + var t = create(name); + t.restart(); + return t; +} "; //" -void initThreadModule() +MonsterObject *trdSing; + +class Kill : IdleFunction +{ + IS initiate(Thread *t) + { + auto mo = params.obj; + + if(mo !is trdSing) + { + // Check if this is another thread + auto trd = getOwner(mo); + if(trd !is t) + { + // It is. Kill it explicitly and return. + trd.kill(); + return IS.Return; + } + } + + // If it's our own thread, tell the scheduler to kill it from + // the inside. + return IS.Kill; + } +} + +class Pause : IdleFunction +{ + IS initiate(Thread *t) + { + auto mo = params.obj; + + // Can only run on the singleton object + if(mo !is trdSing) + fail("Can only pause our own thread"); + + // Move the thread to the 'paused' list + t.moveTo(&scheduler.paused); + + return IS.Manual; + } +} + +Thread *getOwner() { return getOwner(params.obj); } +Thread *getOwner(MonsterObject *mo) +{ + assert(mo !is null); + if(mo is trdSing) + fail("Cannot run this function on the global singleton thread."); + auto trd = cast(Thread*) mo.getExtra(_threadClass).vptr; + assert(trd !is null); + assert(!trd.isRunning || !trd.isPaused, + "thread cannot be running and paused at the same time"); + return trd; +} + +MonsterObject *createObj(Thread *trd) +{ + assert(trd !is null); + auto mo = _threadClass.createObject(); + mo.getExtra(_threadClass).vptr = trd; + return mo; +} + +void create() { - static MonsterClass mc; + // Can only run on the singleton object + if(params.obj !is trdSing) + fail("Can only use create() on the global thread object."); + + char[] name = stack.popString8(); + + assert(cthread !is null); + + // This is a dirty hack that's only needed because we haven't + // implemented function pointers yet. Gets the caller object. + assert(cthread.fstack.list.length >= 2); + auto nd = cthread.fstack.cur; + nd = cthread.fstack.list.getNext(nd); + if(nd.getCls() is _threadClass) + nd = cthread.fstack.list.getNext(nd); + auto mo = nd.obj; + + // Find the function + auto fn = mo.cls.findFunction(name); - if(mc !is null) + if(fn.paramSize > 0) + fail("create(): function " ~ name ~ " cannot have parameters"); + + // Create a new thread + Thread *trd = Thread.getNew(); + + // Schedule the thread run the next frame + trd.pushFunc(fn, mo); + assert(trd.isPaused); + + // This will mess with the stack frame though, so set it up + // correctly. + trd.fstack.cur.frame = stack.getStartInt(0); + cthread.fstack.restoreFrame(); + + stack.pushObject(createObj(trd)); +} + +// Resume is used to restore a thread that was previously paused. It +// will enter the thread immediately, like call. If you wish to run it +// later, use restart instead. +class Resume : IdleFunction +{ + override: + IS initiate(Thread *t) + { + if(params.obj is trdSing) + fail("Cannot use resume() on our own thread."); + + // Get the thread we're resuming + auto trd = getOwner(); + + if(trd is t) + fail("Cannot use resume() on our own thread."); + + if(trd.isDead) + fail("Cannot resume a dead thread."); + + if(!trd.isPaused) + fail("Can only use resume() on paused threads"); + + // Background the current thread. Move it to the pause list + // first, so background doesn't inadvertently delete it. + t.moveTo(&scheduler.paused); + t.background(); + assert(!t.isDead); + + // Reenter the thread + trd.reenter(); + assert(cthread is null); + + // Put the old thread in the forground again + t.foreground(); + + // Make the thread transient again + t.moveTo(&scheduler.transient); + + // Return to sender + return IS.Return; + } + + void abort(Thread *t) + { + fail("Cannot abort thread while it is calling another thread"); + } +} + +class Wait : IdleFunction +{ + override: + IS initiate(Thread *t) + { + if(params.obj is trdSing) + fail("Cannot use wait on our own thread."); + + // Get the thread we're resuming + auto trd = getOwner(); + + if(trd is t) + fail("Cannot use wait on our own thread."); + + // Return immediately if the thread is dead + if(trd.isDead) + return IS.Return; + + t.idleData.vptr = trd; + + return IS.Poll; + } + + bool hasFinished(Thread *t) + { + return (cast(Thread*)t.idleData.vptr).isDead; + } +} + +void restart() +{ + auto trd = getOwner(); + + if(trd.isDead) + fail("Cannot restart a dead thread"); + + if(!trd.isPaused) + fail("Can only use restart() on paused threads"); + + // Move to the runlist + trd.moveTo(scheduler.runNext); +} + +void isDead() +{ stack.pushBool(getOwner().isDead); } + +void isIdle() +{ stack.pushBool(getOwner().fstack.isIdle); } + +void isPaused() +{ stack.pushBool(getOwner().isPaused); } + +void isScheduled() +{ stack.pushBool(getOwner().isScheduled); } + +MonsterClass _threadClass; + +void initThreadModule() +{ + if(_threadClass !is null) return; - mc = new MonsterClass(MC.String, moduleDef, "thread"); + _threadClass = new MonsterClass(MC.String, moduleDef, "thread"); + trdSing = _threadClass.getSing(); + + _threadClass.bind("kill", new Kill); + _threadClass.bind("resume", new Resume); + _threadClass.bind("pause", new Pause); + _threadClass.bind("wait", new Wait); + + _threadClass.bind("create", &create); + _threadClass.bind("restart", &restart); + + _threadClass.bind("isDead", &isDead); + _threadClass.bind("isIdle", &isIdle); + _threadClass.bind("isPaused", &isPaused); + _threadClass.bind("isScheduled", &isScheduled); } -*/ diff --git a/monster/modules/timer.d b/monster/modules/timer.d index 42abcd62b..0ee82f19b 100644 --- a/monster/modules/timer.d +++ b/monster/modules/timer.d @@ -26,7 +26,7 @@ idle sleep(float secs); class IdleSleep_SystemClock : IdleFunction { override: - bool initiate(Thread* cn) + IS initiate(Thread* cn) { // Get the parameter float secs = stack.popFloat; @@ -38,7 +38,7 @@ class IdleSleep_SystemClock : IdleFunction cn.idleData.l += secs*TicksPerSecond; // Schedule us - return true; + return IS.Poll; } bool hasFinished(Thread* cn) @@ -56,7 +56,7 @@ class IdleSleep_SystemClock : IdleFunction class IdleSleep_Timer : IdleFunction { override: - bool initiate(Thread* cn) + IS initiate(Thread* cn) { // The timer is stored in the object's 'extra' pointer auto t = cast(SleepManager)cn.extraData.obj; @@ -66,7 +66,7 @@ class IdleSleep_Timer : IdleFunction cn.idleData.l = t.current + cast(long)(t.tickSize*stack.popFloat); // Schedule us - return true; + return IS.Poll; } bool hasFinished(Thread* cn) diff --git a/monster/util/freelist.d b/monster/util/freelist.d index 10cacff9f..2cfc55583 100644 --- a/monster/util/freelist.d +++ b/monster/util/freelist.d @@ -391,9 +391,14 @@ struct FreeList(T) // Get the first element in the list T* getHead() { return &nodes.getHead().value; } - // Loop through the structs in this list - int opApply(int delegate(ref T) dg) + // Get the next element in the list + T* getNext(T* nd) { - return nodes.opApply(dg); + auto node = cast(TNode*)nd; + return cast(T*) node.getNext(); } + + // Loop through the structs in this list + int opApply(int delegate(ref T) dg) + { return nodes.opApply(dg); } } diff --git a/monster/vm/fstack.d b/monster/vm/fstack.d index d01ca374f..2136155d1 100644 --- a/monster/vm/fstack.d +++ b/monster/vm/fstack.d @@ -28,17 +28,22 @@ import monster.vm.mobject; import monster.vm.mclass; import monster.vm.stack; import monster.vm.error; +import monster.vm.thread; import monster.compiler.states; import monster.compiler.functions; import monster.compiler.linespec; +import monster.util.freelist; + +import std.stdio; +import std.string; + // "friendly" parameter and stack handling. enum SPType { Function, // A function (script or native) Idle, // Idle function State, // State code - NConst, // Native constructor } // One entry in the function stack @@ -59,7 +64,7 @@ struct StackPoint // Could have an afterStack to check that the function has the // correct imprint (corresponding to an imprint-var in Function.) - int *frame; // Stack frame, stored when entering the function + int *frame; // Stack frame for this function // Get the class owning the function MonsterClass getCls() @@ -78,6 +83,15 @@ struct StackPoint bool isState() { return ftype == SPType.State; } + bool isIdle() + { return ftype == SPType.Idle; } + + bool isNative() + { return isFunc && func.isNative; } + + bool isNormal() + { return isState || (isFunc && func.isNormal); } + // Get the current source position (file name and line // number). Mostly used for error messages. Floc getFloc() @@ -92,68 +106,149 @@ struct StackPoint int pos = code.getPos() - 1; if(pos < 0) pos = 0; - fl.line = findLine(func.lines, pos); + if(isFunc) + fl.line = findLine(func.lines, pos); + else + fl.line = findLine(state.lines, pos); return fl; } -} -FunctionStack fstack; + char[] toString() + { + assert(func !is null); + + char[] type, cls, name; + cls = getCls().name.str; + if(isState) + { + type = "state"; + name = state.name.str; + } + else + { + assert(isFunc); + name = func.name.str; + + if(isIdle) type = "idle"; + else type = "function"; + } + + // Function location and name + return format("%s %s.%s", type, cls, name); + } +} -// 30 is somewhat small, but suitable for debugging. -StackPoint fslist[30]; +alias FreeList!(StackPoint) StackList; +alias StackList.TNode *StackNode; struct FunctionStack { + private: + // Guard against infinite recursion + static const maxStack = 100; + + // Number of native functions on the stack + int natives; + + public: + + StackList list; + // The current entry StackPoint *cur = null; - // Index of next entry - int next = 0; - // Consistancy checks invariant() { - assert(next >= 0); - if(next > 0) + if(cur !is null) { - assert(cur !is null); + assert(list.length > 0); if(cur.ftype == SPType.State) - assert(next == 1); + assert(list.length == 1); } - else assert(cur is null); + else assert(list.length == 0); + } + + // Set the global stack frame pointer to correspond to the current + // entry in the fstack. Must be called when putting a thread in the + // foreground. + void restoreFrame() + { + if(cur !is null) + stack.setFrame(cur.frame); + else + stack.setFrame(null); + } + + void killAll() + { + assert(natives == 0); + + while(list.length) + { + assert(cur !is null); + list.remove(cur); + cur = list.getHead(); + } + assert(cur is null); + } + + bool hasNatives() { return natives != 0; } + + // Check if the function calling us is a normal function + bool isNormal() + { + return cur !is null && cur.isNormal; } // Is the function stack empty? - bool isEmpty() { return next == 0; } + bool isEmpty() { return cur is null; } + + bool isIdle() { return cur !is null && cur.ftype == SPType.Idle; } // Are we currently running state code? - bool isStateCode() { return next == 1 && cur.ftype == SPType.State; } + bool isStateCode() { return list.length == 1 && cur.ftype == SPType.State; } // Sets up the next stack point and assigns the given object private void push(MonsterObject *obj) { - if(next >= fslist.length) + if(list.length >= maxStack) fail("Function stack overflow - infinite recursion?"); - cur = &fslist[next++]; - cur.obj = obj; + assert(cur is null || !cur.isIdle, + "Cannot call other script functions from an idle function"); + // Puts a new node at the beginning of the list + cur = list.getNew(); + cur.obj = obj; cur.frame = stack.setFrame(); } // Set the stack point up as a function. Allows obj to be null. void push(Function *func, MonsterObject *obj) { + //assert(cthread !is null && this is &cthread.fstack); + push(obj); + + assert(func !is null); cur.ftype = SPType.Function; cur.func = func; + /* + writefln("Pushing ", func.name); + writefln(toString, "\n"); + */ + + assert(func.owner !is null); assert(obj is null || func.owner.parentOf(obj.cls)); // Point the code stream to the byte code, if any. if(func.isNormal) cur.code.setData(func.bcode); + else if(func.isNative) + natives++; assert(!func.isIdle, "don't use fstack.push() on idle functions"); } @@ -162,46 +257,74 @@ struct FunctionStack void push(State *st, MonsterObject *obj) { assert(st !is null); + assert(isEmpty, + "state code can only run at the bottom of the function stack"); + + //writefln("Pushing state ", st.name); push(obj); cur.ftype = SPType.State; cur.state = st; assert(obj !is null); + assert(st.owner !is null); assert(st.owner.parentOf(obj.cls)); // Set up the byte code cur.code.setData(st.bcode); } - // Native constructor - void pushNConst(MonsterObject *obj) + void pushIdle(Function *func, MonsterObject *obj) { - assert(obj !is null); push(obj); - cur.ftype = SPType.NConst; - } - - void pushIdle(Function *fn, MonsterObject *obj) - { - push(obj); - cur.func = fn; + assert(func !is null); + cur.func = func; cur.ftype = SPType.Idle; - assert(obj is null || fn.owner.parentOf(obj.cls)); - assert(fn.isIdle, fn.name.str ~ "() is not an idle function"); + //writefln("Pushing idle ", func.name); + + assert(func.owner !is null); + assert(obj is null || func.owner.parentOf(obj.cls)); + assert(func.isIdle, func.name.str ~ "() is not an idle function"); } // Pops one entry of the stack. Checks that the stack level has been // returned to the correct position. void pop() { - if(next == 0) + if(isEmpty) fail("Function stack underflow"); - stack.setFrame(cur.frame); + assert(list.length >= 1); + + if(cur.isNative) + natives--; + assert(natives >= 0); + + /* + if(cur.isFunc) writefln("popping function ", cur.func.name); + else if(cur.isState) writefln("popping state ", cur.state.name); + writefln(toString, "\n"); + */ + + // Remove the topmost node from the list, and set cur. + assert(cur == list.getHead()); + list.remove(cur); + cur = list.getHead(); + + restoreFrame(); + + assert(list.length != 0 || cur is null); + } + + // Get a stack trace (pretty basic at the moment) + char[] toString() + { + char[] res; + + foreach(ref c; list) + res = c.toString ~ '\n' ~ res; - if(--next > 0) cur--; - else cur = null; + return "Trace:\n" ~ res; } } diff --git a/monster/vm/idlefunction.d b/monster/vm/idlefunction.d index aa80227ad..21aa00c85 100644 --- a/monster/vm/idlefunction.d +++ b/monster/vm/idlefunction.d @@ -26,6 +26,18 @@ module monster.vm.idlefunction; import monster.vm.thread; +// Idle scheduling actions - returned from initiate() +enum IS + { + Poll, // Poll the hasFinished function regularily + Return, // Return to the thread immediately - reenter() is called + // first + Kill, // Kill the thread + Manual, // Handle the scheduling ourselves. We have to schedule + // or move the thread to another ThreadList before the + // end of initiate() + } + // A callback class for idle functions. A child object of this class // is what you "bind" to idle functions (rather than just a delegate, // like for native functions.) Note that instances are not bound to @@ -43,7 +55,7 @@ abstract class IdleFunction // idea. For functions which never "return", and for event driven // idle functions (which handle their own scheduling), you should // return false. - bool initiate(Thread*) { return true; } + abstract IS initiate(Thread*); // This is called whenever the idle function is about to "return" to // state code. It has to push the return value, if any, but @@ -65,5 +77,5 @@ abstract class IdleFunction // should return false in initiate and instead reschedule the object // manually when the event occurs. (A nice interface for this has // not been created yet, though.) - abstract bool hasFinished(Thread*); + bool hasFinished(Thread*) { assert(0, "empty hasFinished()"); } } diff --git a/monster/vm/mclass.d b/monster/vm/mclass.d index 4c20cdfd9..6d1b2e045 100644 --- a/monster/vm/mclass.d +++ b/monster/vm/mclass.d @@ -36,7 +36,6 @@ import monster.compiler.enums; import monster.vm.codestream; import monster.vm.idlefunction; -import monster.vm.fstack; import monster.vm.arrays; import monster.vm.error; import monster.vm.vm; @@ -327,6 +326,7 @@ final class MonsterClass assert(0); } + assert(fn !is null); return fn; } @@ -338,26 +338,26 @@ final class MonsterClass void bindConst(dg_callback nf) { - assert(constType == FuncType.Native, + assert(natConst.ftype == FuncType.Native, "Cannot set native constructor for " ~ toString ~ ": already set"); - constType = FuncType.NativeDDel; - dg_const = nf; + natConst.ftype = FuncType.NativeDDel; + natConst.natFunc_dg = nf; } void bindConst(fn_callback nf) { - assert(constType == FuncType.Native, + assert(natConst.ftype == FuncType.Native, "Cannot set native constructor for " ~ toString ~ ": already set"); - constType = FuncType.NativeDFunc; - fn_const = nf; + natConst.ftype = FuncType.NativeDFunc; + natConst.natFunc_fn = nf; } void bindConst_c(c_callback nf) { - assert(constType == FuncType.Native, + assert(natConst.ftype == FuncType.Native, "Cannot set native constructor for " ~ toString ~ ": already set"); - constType = FuncType.NativeCFunc; - c_const = nf; + natConst.ftype = FuncType.NativeCFunc; + natConst.natFunc_c = nf; } @@ -572,19 +572,10 @@ final class MonsterClass foreach(c; tree) { // Custom native constructor - if(c.constType != FuncType.Native) - { - fstack.pushNConst(obj); - if(c.constType == FuncType.NativeDDel) - c.dg_const(); - else if(c.constType == FuncType.NativeDFunc) - c.fn_const(); - else if(c.constType == FuncType.NativeCFunc) - c.c_const(); - fstack.pop(); - } + if(c.natConst.ftype != FuncType.Native) + natConst.call(obj); - // TODO: Call script-constructor here + // TODO: Call script constructors here } // Set the same state as the source @@ -732,16 +723,8 @@ final class MonsterClass EnumDeclaration[] enumdecs; ImportStatement[] imports; - // Native constructor type. Changed when the actual constructor is - // set. - FuncType constType = FuncType.Native; - union - { - dg_callback dg_const; - fn_callback fn_const; - c_callback c_const; - } - + // Native constructor, if any + Function natConst; /******************************************************* * * @@ -957,13 +940,17 @@ final class MonsterClass parse(tokens, fname); } - // Parses a list of tokens + // Parses a list of tokens, and do other setup. void parse(ref TokenArray tokens, char[] fname) { assert(!isParsed(), "parse() called on a parsed class " ~ name.str); alias Block.isNext isNext; + natConst.ftype = FuncType.Native; + natConst.name.str = "native constructor"; + natConst.owner = this; + // TODO: Check for a list of keywords here. class, module, // abstract, final. They can come in any order, but only certain // combinations are legal. For example, class and module cannot diff --git a/monster/vm/mobject.d b/monster/vm/mobject.d index 274eb11eb..af9a13718 100644 --- a/monster/vm/mobject.d +++ b/monster/vm/mobject.d @@ -234,16 +234,6 @@ struct MonsterObject return &data[treeIndex][pos]; } - /* KILLME - // Get a long (two ints) from the data segment - long *getDataLong(int pos) - { - if(pos < 0 || pos+1>=data.length) - fail("MonsterObject: data pointer out of range: " ~ toString(pos)); - return cast(long*)&data[pos]; - } - */ - // Get an array from the data segment int[] getDataArray(int treeIndex, int pos, int len) { @@ -317,19 +307,13 @@ struct MonsterObject // Do we already have a thread? if(sthread !is null) { - // If we are already scheduled (if an idle function has - // scheduled us, or if setState has been called multiple - // times), unschedule. This will automatically cancel any - // scheduled idle functions and call their abort() functions. - if(sthread.isScheduled) - sthread.cancel(); - - assert(sthread.isActive || !sthread.stateChange, - "stateChange was set outside active code"); - - // If we are running from state code, signal it that we must - // now abort execution when we reach the state level. - sthread.stateChange = sthread.isActive; + // Check if the thread has gone and died on us while we were + // away. + if(sthread.isDead) + sthread = null; + else + // Still alive. Stop any execution of the thread + sthread.stop(); } // If we are jumping to anything but the empty state, we will have @@ -349,18 +333,24 @@ struct MonsterObject { // Make sure there's a thread to run in if(sthread is null) - sthread = Thread.getNew(this); + sthread = Thread.getNew(); - // Schedule the thread to start at the given label - sthread.schedule(label.offs); + // Schedule the thread to start at the given state and + // label + sthread.scheduleState(this, label.offs); assert(sthread.isScheduled); } } - // Don't leave an unused thread dangling - kill it instead. - if(sthread !is null && sthread.isUnused) + // If nothing is scheduled, kill the thread + if(sthread !is null && !sthread.isScheduled) { + assert(sthread.isTransient); sthread.kill(); + + // Zero out any pointers to the thread. + if(sthread is cthread) + cthread = null; sthread = null; } diff --git a/monster/vm/params.d b/monster/vm/params.d index 3de1894ae..752cb5829 100644 --- a/monster/vm/params.d +++ b/monster/vm/params.d @@ -24,7 +24,7 @@ module monster.vm.params; import monster.vm.mobject; -import monster.vm.fstack; +import monster.vm.thread; /* This module offers a "friendly" interface for dealing with parameters and return values on the stack. It is meant to be an @@ -44,8 +44,9 @@ struct Params // function) MonsterObject *obj() { - assert(fstack.cur !is null); - assert(fstack.cur.obj !is null); - return fstack.cur.obj; + assert(cthread !is null); + assert(cthread.fstack.cur !is null); + assert(cthread.fstack.cur.obj !is null); + return cthread.fstack.cur.obj; } } diff --git a/monster/vm/stack.d b/monster/vm/stack.d index 1802b867e..cf0ed9f26 100644 --- a/monster/vm/stack.d +++ b/monster/vm/stack.d @@ -33,7 +33,6 @@ import monster.compiler.scopes; import monster.vm.mobject; import monster.vm.mclass; import monster.vm.arrays; -import monster.vm.fstack; import monster.vm.error; // Stack. There's only one global instance, but threads will make @@ -74,21 +73,19 @@ struct CodeStack return total-left; } - // Sets the current position as the 'frame pointer', and return - // previous value. + // Sets the current position as the 'frame pointer', and return it. int *setFrame() { - auto old = frame; frame = pos; fleft = left; //writefln("setFrame(): new=%s, old=%s", frame, old); - return old; + return frame; } // Sets the given frame pointer void setFrame(int *frm) { - //writefln("setFrame(%s)", frm); + //writefln("restoring frame: %s", frm); frame = frm; if(frm is null) { @@ -99,18 +96,14 @@ struct CodeStack assert(fleft >= 0 && fleft <= total); } - // Reset the stack level to zero. Should only be called when the - // frame pointer is already zero (we use it in state code only.) + // Reset the stack level to zero. void reset() { left = total; pos = data.ptr; - if(fleft != 0) - writefln("left=%s total=%s fleft=%s", left, total, fleft); - assert(frame is null); - assert(fleft == 0); - assert(fstack.isEmpty); + fleft = 0; + frame = null; } void pushInt(int i) @@ -145,6 +138,15 @@ struct CodeStack return *(cast(long*)pos); } + // Get the pointer from the start of the stack + int *getStartInt(int pos) + { + if(pos < 0 || pos >= getPos) + fail("CodeStack.getStartInt() pointer out of range"); + + return &data[pos]; + } + // Get the pointer to an int at the given position backwards from // the current stack pointer. 0 means the first int, ie. the one we // would get if we called popInt. 1 is the next, etc diff --git a/monster/vm/thread.d b/monster/vm/thread.d index 1b932e7fb..b4eaea361 100644 --- a/monster/vm/thread.d +++ b/monster/vm/thread.d @@ -78,16 +78,16 @@ struct Thread // temporary data off the stack. SharedType idleData; - // The contents of idleObj's extra data for the idle's owner class. + // The contents of the idle object's extra data for the idle's owner + // class. SharedType extraData; - // Set to true whenever we are running from state code. If we are - // inside the state itself, this will be true and 'next' will be 1. - bool isActive; - // Set to true when a state change is in progress. Only used when // state is changed from within a function in active code. - bool stateChange; + bool shouldExit; + + // Function stack for this thread + FunctionStack fstack; /******************************************************* * * @@ -96,21 +96,11 @@ struct Thread *******************************************************/ private: - // Temporarily needed since we need a state and an object to push on - // the stack to return to state code. This'll change soon (we won't - // need to push anything to reenter, since the function stack will - // already be set up for us.) - MonsterObject * theObj; - - Function *idle; - MonsterObject *idleObj; // Object owning the idle function - NodeList * list; // List owning this thread - int retPos; // Return position in byte code. + NodeList *list; // List owning this thread // Stored copy of the stack. Used when the thread is not running. int[] sstack; - public: /******************************************************* * * @@ -118,91 +108,120 @@ struct Thread * * *******************************************************/ - // Get a new thread. It starts in the 'unused' list. - static Thread* getNew(MonsterObject *obj = null) + // Get a new thread. It starts in the 'transient' list. + static Thread* getNew() { - auto cn = scheduler.unused.getNew(); - cn.list = &scheduler.unused; + auto cn = scheduler.transient.getNew(); + cn.list = &scheduler.transient; with(*cn) { - theObj = obj; - // Initialize other variables - idle = null; - idleObj = null; - isActive = false; - stateChange = false; - retPos = -1; + shouldExit = false; sstack = null; } - /* - if(obj !is null) - writefln("Got a new state thread"); - else - writefln("Got a new non-state thread"); - */ - return cn; } - // Unschedule this node from the runlist or waitlist it belongs to, - // but don't kill it. Any idle function connected to this node is - // aborted. - void cancel() + // Stop the thread and return it to the freelist + void kill() { - if(idle !is null) + stop(); + assert(fstack.isEmpty); + list.remove(this); + list = null; + assert(isDead); + } + + // Stop the execution of a thread and cancel any scheduling. + void stop() + { + assert(!isDead); + + // TODO: We also have to handle (forbid) cases where we are + // the caller of another thread. + + if(isRunning) { - fstack.pushIdle(idle, idleObj); - idle.idleFunc.abort(this); - fstack.pop(); - idle = null; + // We are running. + assert(sstack.length == 0); + + // Forbid stopping the thread if there are native functions on + // the function stack. + if(fstack.hasNatives) + fail("Cannot stop thread, there are native functions on the stack."); + + // Kill the stack tell execute() to stop running + stack.reset(); + shouldExit = true; + } + else + { + // We are not running + + // Free the stack buffers + if(sstack.length) + Buffers.free(sstack); + sstack = null; + + // Abort any idle function + if(fstack.isIdle) + { + // Abort the idle function and pop it + getIdle().abort(this); + fstack.pop(); + } + assert(!fstack.hasNatives); } - retPos = -1; - moveTo(&scheduler.unused); + // Kill the function stack + fstack.killAll(); + + // Move to the transient list (signalling that the thread is + // unused.) + moveTo(&scheduler.transient); assert(!isScheduled); } - // Remove the thread comletely - void kill() + // Schedule this thread to run state code the next frame + void scheduleState(MonsterObject *obj, int offs) { - /* - if(theObj is null) - writefln("Killing non-state thread"); - else - writefln("Killing state thread"); - */ + assert(!isDead); + assert(!isScheduled, + "cannot schedule an already scheduled thread"); + assert(!fstack.isIdle); + assert(fstack.isEmpty); + assert(offs >= 0); + assert(obj !is null); - cancel(); - list.remove(this); - list = null; + assert(isRunning == shouldExit); - if(sstack.length) - Buffers.free(sstack); - sstack = null; - - /* - writefln("Thread lists:"); - writefln(" run: ", scheduler.run.length); - writefln(" runNext: ", scheduler.runNext.length); - writefln(" wait: ", scheduler.wait.length); - writefln(" unused: ", scheduler.unused.length); - */ - } + // Move to the runlist + moveTo(scheduler.runNext); - bool isDead() { return list is null; } + // Set up the function stack + fstack.push(obj.state, obj); + fstack.cur.code.jump(offs); + } - // Schedule this thread to run next frame - void schedule(int offs) + // Push a function and pause the thread + void pushFunc(Function *fn, MonsterObject *obj) { + assert(!isDead); assert(!isScheduled, "cannot schedule an already scheduled thread"); + assert(fstack.isEmpty); + assert(!fstack.isIdle); + assert(fn !is null); + assert(obj !is null); + assert(!fn.isIdle); - retPos = offs; - assert(offs >= 0); - moveTo(scheduler.runNext); + // Set up the function stack + assert(fn.owner.parentOf(obj)); + fstack.push(fn, obj); + + moveTo(&scheduler.paused); } // Are we currently scheduled? @@ -211,17 +230,15 @@ struct Thread // The node is per definition scheduled if it is in one of these // lists return - list is &scheduler.wait || - list is scheduler.run || - list is scheduler.runNext; + !isDead && (list is &scheduler.wait || + list is scheduler.run || + list is scheduler.runNext); } - bool isUnused() - { - return list is &scheduler.unused; - } - - bool isIdle() { return idle !is null; } + bool isTransient() { return list is &scheduler.transient; } + bool isRunning() { return cthread is this; } + bool isDead() { return list is null; } + bool isPaused() { return list is &scheduler.paused; } // Get the next node in the freelist Thread* getNext() @@ -237,120 +254,122 @@ struct Thread // Reenter this thread to the point where it was previously stopped. void reenter() { - assert(theObj !is null, - "cannot reenter a non-state thread yet"); - - // Most if not all of these checks will have to be removed in the - // future - assert(theObj.state !is null, "attempted to call the empty state"); - assert(!isActive, - "reenter cannot be called when object is already active"); - assert(fstack.isEmpty, - "can only reenter at the bottom of the function stack"); - assert(isScheduled); - - if(isIdle) + assert(!isDead); + assert(cthread is null, + "cannot reenter when another thread is running"); + assert(!isRunning, + "reenter cannot be called when thread is already running"); + assert(isScheduled || isPaused); + assert(!fstack.isEmpty); + + // Put the thread in the foreground + foreground(); + assert(isRunning); + + // Notify the idle function, if any + if(fstack.isIdle) { - assert(idle !is null); - assert(idleObj !is null || idle.isStatic); - // Tell the idle function that we we are reentering - fstack.pushIdle(idle, idleObj); - idle.idleFunc.reentry(this); - fstack.pop(); + assert(fstack.isIdle); + getIdle().reentry(this); - // We're no longer idle - idle = null; + // Remove the idle function + fstack.pop(); } - // Set the active flat to indicate that we are now actively - // running. (Might not be needed in the future) - isActive = true; - - // Set the thread - assert(cthread is null); - cthread = this; + assert(fstack.cur.isNormal, + "can only reenter script code"); // Remove the current thread from the run list - moveTo(&scheduler.unused); - - // Restore the stack - restoreStack(); - - // Set up the code stack for state code. - fstack.push(theObj.state, theObj); - - // Set the position - assert(retPos >= 0); - fstack.cur.code.jump(retPos); + moveTo(&scheduler.transient); // Run the code execute(); - // Reset the thread - cthread = null; - - fstack.pop(); + // Exit immediately if the thread committed suicide. + if(isDead) return; - // We are no longer active - isActive = false; + shouldExit = false; - assert(stack.getPos == 0, - format("Stack not returned to zero after state code, __STACK__=", - stack.getPos)); - - if(!isUnused) - // Store the stack - acquireStack(); - else - // If the thread is not used for anything, might as well kill it - kill(); + // Background the thread + background(); } - // Make a copy of the stack and store it for later. Reset the global - // stack. - void acquireStack() + // Put this thread in the background. Acquires the stack and + // function stack. + void background() { - assert(!isUnused(), - "unused threads should never need to aquire the stack"); + assert(!isDead); assert(sstack.length == 0, "Thread already has a stack"); - assert(fstack.isEmpty); + assert(isRunning, + "cannot put a non-running thread in the background"); + assert(!fstack.hasNatives); + + // We're no longer the current thread + cthread = null; - // This can be optimized later - int len = stack.getPos(); - if(len) + if(isTransient) { - writefln("acquiring %s ints", len); + // The thread is not scheduled and will not be used + // anymore. Might as well kill it. + assert(!isRunning); + kill(); + } + else + { + // The thread will possibly be restored at some point. Store + // the stack and fstack for later. - // Get a new buffer, and copy the stack - sstack = Buffers.getInt(len); - sstack[] = stack.popInts(len); + // Stack. + int len = stack.getPos(); + if(len) + { + // Get a new buffer, and copy the stack + sstack = Buffers.getInt(len); + sstack[] = stack.popInts(len); + } } + // Clear out our stack values stack.reset(); - } - private: - /******************************************************* - * * - * Private helper functions * - * * - *******************************************************/ + assert(!isRunning); + } - void restoreStack() + // Put the thread in the foreground. Restore any stored stack + // values. + void foreground() { - assert(stack.getPos() == 0, - "cannot restore into a non-empty stack"); + assert(!isDead); + assert(cthread is null, + "cannot restore thread, another thread is running"); + assert(!isRunning, + "cannot restore thread, it is already running"); + + assert((isTransient && fstack.isEmpty) || + stack.getPos() == 0, + "only empty transient threads kan restore into a non-empty stack"); if(sstack.length) { + assert(stack.getPos() == 0, + "cannot restore into a non-empty stack"); + assert(!isTransient, + "cannot restore a transent thread with stack"); + // Push the values back, and free the buffer stack.pushInts(sstack); Buffers.free(sstack); assert(stack.getPos == sstack.length); sstack = null; } + + // Restore the stack frame pointer + fstack.restoreFrame(); + + // Set ourselves as the running thread + cthread = this; } // Move this node to another list. @@ -361,6 +380,21 @@ struct Thread list = to; } + private: + /******************************************************* + * * + * Private helper functions * + * * + *******************************************************/ + + IdleFunction getIdle() + { + assert(fstack.isIdle); + assert(fstack.cur.func !is null); + assert(fstack.cur.func.idleFunc !is null); + return fstack.cur.func.idleFunc; + } + void fail(char[] msg) { Floc fl; @@ -371,61 +405,72 @@ struct Thread } // Parse the BC.CallIdle instruction parameters and schedule the - // given idle function. - void callIdle(MonsterObject *iObj) + // given idle function. Return true if we should exit execute() + bool callIdle(MonsterObject *iObj) { - assert(isActive && fstack.isStateCode, - "Byte code attempted to call an idle function outside of state code."); assert(!isScheduled, "Thread is already scheduled"); + assert(isRunning); + assert(!isScheduled, "Thread is already scheduled"); + assert(iObj !is null); - CodeStream *code = &fstack.cur.code; + if(fstack.hasNatives) + fail("Cannot run idle function: there are native functions on the stack"); - // Store the object - idleObj = iObj; - assert(idleObj !is null); + CodeStream *code = &fstack.cur.code; // Get the class from the index auto cls = iObj.cls.upcast(code.getInt()); // Get the function - idle = cls.findFunction(code.getInt()); + Function *idle = cls.findFunction(code.getInt()); assert(idle !is null && idle.isIdle); assert(cls is idle.owner); - assert(idleObj.cls.childOf(cls)); + assert(iObj.cls.childOf(cls)); // The IdleFunction object bound to this function is stored in // idle.idleFunc if(idle.idleFunc is null) fail("Called unimplemented idle function '" ~ idle.name.str ~ "'"); - // Set the return position - retPos = fstack.cur.code.getPos(); - // Set up extraData - extraData = *idleObj.getExtra(idle.owner); + extraData = *iObj.getExtra(idle.owner); - // Notify the idle function - fstack.pushIdle(idle, idleObj); - if(idle.idleFunc.initiate(this)) - moveTo(&scheduler.wait); - fstack.pop(); - } + // Push the idle function on the stack, with iObj as the 'this' + // object. + fstack.pushIdle(idle, iObj); - bool shouldExit() - { - if(fstack.isStateCode && stateChange) + // Notify the idle function that it was called + auto res = idle.idleFunc.initiate(this); + //writefln("Called %s, result was %s", idle.name, res); + + if(res == IS.Poll) { - assert(isActive); + moveTo(&scheduler.wait); + return true; + } - // The state was changed while in state code. Abort the code. - stateChange = false; + if(res == IS.Return) + { + // If we're returning, call reenter immediately + idle.idleFunc.reentry(this); - // There might be dangling stack values - stack.reset(); - return true; + // The function is done, pop it back of the stack + fstack.pop(); + + // 'false' means continue running + return false; } - return false; - } + assert(res == IS.Manual || res == IS.Kill); + + // The only difference between Manual and Kill is what list the + // thread ends in. If the thread is in the transient list, it will + // be killed automatically when it's no longer running. + assert( (res == IS.Kill) == isTransient, + res == IS.Manual ? "Manually scheduled threads must be moved to another list." : "Killed threads cannot be moved to another list."); + + // 'true' means exit execute() + return true; + } /******************************************************* * * @@ -444,10 +489,12 @@ struct Thread // an infinite loop. static const long limit = 10000000; + assert(!isDead); assert(fstack.cur !is null, "Thread.execute called but there is no code on the function stack."); - - assert(cthread == this, + assert(fstack.cur.isNormal, + "execute() can only run script code"); + assert(isRunning, "can only run the current thread"); // Get some values from the function stack @@ -544,78 +591,97 @@ struct Thread { case BC.Exit: - // Exit execute(). The function stack cleanup is handled - // by Our Glorious Caller. - return; + // Step down once on the function stack + fstack.pop(); - case BC.Call: - { - // Get the correct function from the virtual table - val = code.getInt(); // Class index - auto fn = obj.cls.findVirtualFunc(val, code.getInt()); + if(!fstack.isNormal()) + // The current function isn't a script function, so + // exit. + return; - // Finally, call - fn.call(obj); + assert(!shouldExit); - if(shouldExit()) return; - break; - } + // Set up the variables and continue running. + assert(fstack.cur !is null); + cls = fstack.cur.getCls(); + code = &fstack.cur.code; + obj = fstack.cur.obj; + + break; - case BC.CallFar: + // Start a block so these variables are local { - auto mo = stack.popObject(); + MonsterObject *mo; + Function *fn; + + case BC.Call: + mo = obj; + goto CallCommon; + + case BC.CallFar: + mo = stack.popObject(); + + CallCommon: // Get the correct function from the virtual table val = code.getInt(); // Class index - auto fn = mo.cls.findVirtualFunc(val, code.getInt()); - - // Call the function - fn.call(mo); + fn = mo.cls.findVirtualFunc(val, code.getInt()); - // Exit state code if the state was changed - if(shouldExit()) return; + if(fn.isNormal) + { + // Normal (script) function. We don't need to exit + // execute(), just change the function stack and the + // cls and code pointers. Then keep running. + fstack.push(fn, mo); + cls = fstack.cur.getCls(); + code = &fstack.cur.code; + obj = mo; + assert(obj is fstack.cur.obj); + } + else + { + // Native function. Let Function handle it. + assert(fn.isNative); + fn.call(mo); + if(shouldExit) return; + } break; } case BC.CallIdle: - callIdle(obj); - return; + if(callIdle(obj)) + return; + break; case BC.CallIdleFar: - callIdle(stack.popObject()); - return; + if(callIdle(stack.popObject())) + return; + break; case BC.Return: // Remove the given number of bytes from the stack, and // exit the function. stack.pop(code.getInt()); - return; + goto case BC.Exit; case BC.ReturnVal: stack.pop(code.getInt(), 1); - return; + goto case BC.Exit; case BC.ReturnValN: val = code.getInt(); // Get the value first, since order // of evaluation is important. stack.pop(val, code.getInt()); - return; + goto case BC.Exit; case BC.State: val = code.getInt(); // State index val2 = code.getInt(); // Label index // Get the class index and let setState handle everything obj.setState(val, val2, code.getInt()); - if(shouldExit()) return; + if(shouldExit) return; break; - case BC.Halt: - // A halt statement was encountered, and we should stop - // execution. This is only allowed in state code. - assert(isActive && fstack.isStateCode, - "halt command encountered outside state code."); - return; - case BC.New: // Create a new object. Look up the class index in the // global class table, and create an object from it. @@ -1372,9 +1438,14 @@ struct Scheduler // Waiting list - idle threads that are actively checked each frame. NodeList wait; - // List of unused nodes. Any thread in this list (that is not - // actively running) can and will be deleted eventually. - NodeList unused; + // List of transient nodes. Any thread in this list (that is not + // actively running) can and will be deleted when it goes into the + // background. + NodeList transient; + + // List of threads that are not running or scheduled, but should not + // be deleted. + NodeList paused; // The run lists for this and the next round. We use pointers to the // actual lists, since we want to swap them easily. @@ -1413,6 +1484,8 @@ struct Scheduler // good.) But all this falls in the "optimization" category. void doFrame() { + assert(cthread is null, + "cannot run doFrame while another thread is running"); checkConditions(); dispatch(); } @@ -1436,15 +1509,12 @@ struct Scheduler // way, ie to change object states or interact with the // scheduler. In fact, hasFinished() should do as little as // possible. - if(cn.isIdle) + if(cn.fstack.isIdle) { - fstack.pushIdle(cn.idle, cn.idleObj); - if(cn.idle.idleFunc.hasFinished(cn)) + if(cn.getIdle().hasFinished(cn)) // Schedule the code to start running again this round. We // move it from the wait list to the run list. cn.moveTo(runNext); - - fstack.pop(); } // Set the next item cn = next; @@ -1473,9 +1543,6 @@ struct Scheduler // Execute cn.reenter(); - // The function stack should now be at zero - assert(fstack.isEmpty()); - // Get the next item. cn = run.getHead(); } diff --git a/monster/vm/vm.d b/monster/vm/vm.d index 149a79c07..90f8e5f4e 100644 --- a/monster/vm/vm.d +++ b/monster/vm/vm.d @@ -24,7 +24,6 @@ module monster.vm.vm; import monster.vm.error; -import monster.vm.fstack; import monster.vm.thread; import monster.vm.mclass; import monster.vm.mobject; @@ -47,18 +46,21 @@ struct VM { // Run a script file in the context of the object obj. If no object // is given, an instance of an empty class is used. - void run(char[] file, MonsterObject *obj = null) + Thread *run(char[] file, MonsterObject *obj = null) { + Thread *trd; + auto func = new Function; if(obj !is null) { - auto func = Function(file, obj.cls); - func.call(obj); + *func = Function(file, obj.cls); + trd = func.call(obj); } else { - auto func = Function(file); - func.call(); + *func = Function(file); + trd = func.call(); } + return trd; } void frame(float time = 0) diff --git a/mscripts/fpsticker.mn b/mscripts/fpsticker.mn index 85d239e2d..824199f28 100644 --- a/mscripts/fpsticker.mn +++ b/mscripts/fpsticker.mn @@ -1,17 +1,27 @@ // Small script that prints the FPS to screen with regular intervals. -singleton FPSTicker; -import io, timer; +import io, timer, frames; -ulong lastFrame; +// Sleep one frame. This makes sure that we won't start running until +// the rendering begins. Not critically important, but it prevents the +// first printed value from being 'nan'. +fsleep(1); + +ulong lastFrame = counter; +float lastTime = totalTime; float delay = 1.5; -state tick +while(true) { - begin: sleep(delay); - print("fps:", (frames.counter-lastFrame) / delay); - lastFrame = frames.counter; - goto begin; + + // Calculate differences since last frame + ulong fdiff = counter-lastFrame; + float tdiff = totalTime-lastTime; + + print("fps:", fdiff / tdiff); + + lastFrame = counter; + lastTime = totalTime; } diff --git a/mscripts/object.d b/mscripts/object.d index 776d6eaf0..8a2b2d7e3 100644 --- a/mscripts/object.d +++ b/mscripts/object.d @@ -32,7 +32,6 @@ import core.resource : rnd; import core.config; import sound.music; - // Set up the base Monster classes we need in OpenMW void initMonsterScripts() { @@ -55,11 +54,9 @@ void initMonsterScripts() { stack.pushInt(rnd.randInt (stack.popInt,stack.popInt));}); - // Set up and run the fps ticker - auto mo = (new MonsterClass("FPSTicker")).getSing(); - mo.setState("tick"); + // Run the fps ticker + vm.run("fpsticker.mn"); - // Load and run the test script - mc = new MonsterClass("Test"); - mc.createObject().call("test"); + // Run the test script + vm.run("test.mn"); } diff --git a/mscripts/test.mn b/mscripts/test.mn index f0fdcb8a1..7fde6ff2e 100644 --- a/mscripts/test.mn +++ b/mscripts/test.mn @@ -1,22 +1,12 @@ -// A sample class -class Test; +// A short example script import io, timer; -test() -{ - state = printMessage; -} +sleep(6); -state printMessage +while(true) { - // This state code will run as long as the object is in this state. - begin: - sleep(6); - - loop: print("Howdy from the world of Monster scripts!"); print("This script is located in mscripts/test.mn. Check it out!"); sleep(60); - goto loop; } diff --git a/openmw.d b/openmw.d index ee23b88b7..ca8f7b3bf 100644 --- a/openmw.d +++ b/openmw.d @@ -234,7 +234,6 @@ void main(char[][] args) initBullet(); scope(exit) cleanupBullet(); - if(cd.inCell) { setAmbient(cd.ambi.ambient, cd.ambi.sunlight, @@ -414,7 +413,7 @@ void main(char[][] args) // This isn't necessary but it's here for testing purposes. cellList.release(cd); - // Write some memory statistics + // Write some statistics poolSize(); writefln(esmRegion); writefln("Total objects: ", MonsterClass.getTotalObjects); diff --git a/sound/music.d b/sound/music.d index 9a2585ad0..00990e48c 100644 --- a/sound/music.d +++ b/sound/music.d @@ -38,7 +38,7 @@ import core.resource; class Idle_waitUntilFinished : IdleFunction { override: - bool initiate(Thread*) { return true; } + IS initiate(Thread*) { return IS.Poll; } bool hasFinished(Thread* trd) {