diff --git a/input/events.d b/input/events.d index 7357ff285..52074e142 100644 --- a/input/events.d +++ b/input/events.d @@ -224,16 +224,8 @@ void initializeInput() // put another import in core.config. I should probably check the // bug list and report it. updateMouseSensitivity(); - - // Set up the FPS ticker - auto mo = (new MonsterClass("FPSTicker")).getSing(); - frameCount = mo.getIntPtr("frameCount"); - mo.setState("tick"); } -// Points directly to FPSTicker.frameCounter in Monster -int *frameCount; - extern(C) int ois_isPressed(int keysym); // Check if a key is currently down @@ -247,8 +239,6 @@ bool isPressed(Keys key) extern(C) int d_frameStarted(float time) { - (*frameCount)++; - if(doExit) return 0; // Run the Monster scheduler diff --git a/monster/compiler/functions.d b/monster/compiler/functions.d index 9a7842daa..13ba4341c 100644 --- a/monster/compiler/functions.d +++ b/monster/compiler/functions.d @@ -78,16 +78,32 @@ 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; + LineSpec[] lines; // Line specifications for byte code + union + { + ubyte[] bcode; // Final compiled code (normal functions) + dg_callback natFunc_dg; // Various types of native functions + fn_callback natFunc_fn; + c_callback natFunc_c; + IdleFunction idleFunc; // Idle function callback + } + + Token name; Type type; // Return type FuncType ftype; // Function type - Token name; Variable* params[]; // List of parameters - MonsterClass owner; int index; // Unique function identifier within its class int paramSize; + + /* int imprint; // Stack imprint of this function. Equals - // (type.getSize() - paramSize) (NOT USED YET) + // (type.getSize() - paramSize) (not implemented yet) + */ // Is this function final? (can not be overridden in child classes) bool isFinal; @@ -98,16 +114,6 @@ struct Function // What function we override (if any) Function *overrides; - union - { - ubyte[] bcode; // Final compiled code (normal functions) - dg_callback natFunc_dg; // Various types of native functions - fn_callback natFunc_fn; - c_callback natFunc_c; - IdleFunction idleFunc; // Idle function callback - } - LineSpec[] lines; // Line specifications for byte code - bool isNormal() { return ftype == FuncType.Normal; } bool isNative() { @@ -138,7 +144,7 @@ struct Function // native code. void call(MonsterObject *obj) { - assert(obj !is null); + assert(obj !is null || isStatic); // Make sure there's a thread to use bool wasNew; @@ -187,9 +193,18 @@ struct Function // kill it. if(cthread.isUnused) cthread.kill(); + else + // Otherwise, store the stack + cthread.acquireStack(); cthread = null; + + assert(fstack.isEmpty); } + // 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); } // Call without an object. TODO: Only allowed for functions compiled diff --git a/monster/compiler/states.d b/monster/compiler/states.d index 9810d722c..688971438 100644 --- a/monster/compiler/states.d +++ b/monster/compiler/states.d @@ -38,6 +38,13 @@ 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; + LineSpec[] lines; // Line specifications for byte code + ubyte[] bcode; // Final compiled code + Token name; int index; @@ -45,16 +52,16 @@ struct State HashTable!(char[], StateLabel*) labels; StateLabel* labelList[]; + // Cache the begin label since it has special meaning and is looked + // up often. + StateLabel* begin; + StateScope sc; // Scope for this state - MonsterClass owner; // Class where this state was defined // State declaration - used to resolve forward references. Should // not be kept around when compilation is finished. StateDeclaration stateDec; - ubyte[] bcode; - LineSpec[] lines; - StateLabel* findLabel(char[] name) { StateLabel *lb; @@ -209,6 +216,13 @@ class StateDeclaration : Statement assert(name == sl.name.str, "label name mismatch"); sl.index = cnt++; st.labelList[sl.index] = sl; + + // Cache the 'begin:' label + if(name == "begin") + { + assert(st.begin is null); + st.begin = sl; + } } } diff --git a/monster/modules/all.d b/monster/modules/all.d index 90fa2bc45..52484e3f8 100644 --- a/monster/modules/all.d +++ b/monster/modules/all.d @@ -2,9 +2,11 @@ module monster.modules.all; import monster.modules.io; import monster.modules.timer; +import monster.modules.frames; void initAllModules() { initIOModule(); initTimerModule(); + initFramesModule(); } diff --git a/monster/modules/frames.d b/monster/modules/frames.d new file mode 100644 index 000000000..94b8a9abb --- /dev/null +++ b/monster/modules/frames.d @@ -0,0 +1,88 @@ + +// Provides some simple numbers and functions regarding the rendering +// frames of the application. It's up to the user to some degree to +// provide this information, though. We rely on vm.frame to be called +// each frame. +module monster.modules.frames; + +import monster.monster; +import monster.vm.mclass; +import monster.vm.idlefunction; +import monster.vm.thread; + +const char[] moduleDef = +"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 sleep(int frameNum); +"; //" + +// Keep local copies of these, since we don't want Monster code to +// overwrite them (we'll be able to explicitly forbid this later.) +ulong frames = 0; +float totTime = 0; + +ulong *counter_ptr; +float *time_ptr; +float *totalTime_ptr; + +// Add the given time and number of frames to the counters +void updateFrames(float time, int frmCount = 1) +{ + // Add up to the totals + frames += frmCount; + totTime += time; + + // Set the Monster variables + *counter_ptr = frames; + *time_ptr = time; + *totalTime_ptr = totTime; + + // TODO: A similar priority queue like we're planning for timer + // would also be applicable here. However I'm guessing frameSleep() + // will be used a lot less than sleep() though, so this is really + // not high up on the priority list. +} + +// Idle function that sleeps a given number of frames before +// returning. +class IdleFrameSleep : IdleFunction +{ + override: + bool initiate(Thread* cn) + { + // Calculate the return frame + cn.idleData.l = frames + stack.popInt; + + // Schedule us + return true; + } + + bool hasFinished(Thread* cn) + { + // Are we at (or past) the correct frame? + return frames >= cn.idleData.l; + } +} + +void initFramesModule() +{ + static MonsterClass mc; + if(mc !is null) return; + + mc = new MonsterClass(MC.String, moduleDef, "frames"); + + // Bind the idle + mc.bind("sleep", new IdleFrameSleep); + + // Get pointers to the variables so we can write to them easily. + auto mo = mc.getSing(); + counter_ptr = mo.getUlongPtr("counter"); + time_ptr = mo.getFloatPtr("time"); + totalTime_ptr = mo.getFloatPtr("totalTime"); +} diff --git a/monster/modules/threads.d b/monster/modules/threads.d new file mode 100644 index 000000000..ea3074ec5 --- /dev/null +++ b/monster/modules/threads.d @@ -0,0 +1,25 @@ +// This module provides an interface to the virtual threading API in +// Monster. Not done. + +module monster.modules.threads; + +/* +import monster.monster; + +const char[] moduleDef = +"singleton thread; +native cancel(); +native schedule(); +idle pause(); +"; //" + +void initThreadModule() +{ + static MonsterClass mc; + + if(mc !is null) + return; + + mc = new MonsterClass(MC.String, moduleDef, "thread"); +} +*/ diff --git a/monster/monster.d b/monster/monster.d index f69e98ac7..399d892f9 100644 --- a/monster/monster.d +++ b/monster/monster.d @@ -56,6 +56,6 @@ static this() // Initialize VM scheduler.init(); - initStack(); + stack.init(); arrays.initialize(); } diff --git a/monster/vm/codestream.d b/monster/vm/codestream.d index 762758e45..00bb4707f 100644 --- a/monster/vm/codestream.d +++ b/monster/vm/codestream.d @@ -27,7 +27,6 @@ module monster.vm.codestream; import std.string; import std.stdio; import monster.vm.error; -import monster.compiler.linespec; // CodeStream is a simple utility structure for reading data // sequentially. It holds a piece of byte compiled code, and keeps @@ -39,24 +38,11 @@ struct CodeStream int len; ubyte *pos; - // Position of the last instruction - ubyte *cmdPos; - - // Used to convert position to the corresponding source code line, - // for error messages. - LineSpec[] lines; - - // Size of debug output - const int preView = 50; - const int perLine = 16; - public: - void setData(ubyte[] data, - LineSpec[] lines) + void setData(ubyte[] data) { this.data = data; - this.lines = lines; len = data.length; pos = data.ptr; } @@ -64,34 +50,11 @@ struct CodeStream // Called when the end of the stream was unexpectedly encountered void eos(char[] func) { - char[] res = format("Premature end of input:\nCodeStream.%s() missing %s byte(s)\n", + char[] res = format("Premature end of input: %s() missing %s byte(s)", func, -len); - - res ~= debugString(); - fail(res); } - char[] debugString() - { - int start = data.length - preView; - if(start < 0) start = 0; - - char[] res = format("\nLast %s bytes of byte code:\n", data.length-start); - foreach(int i, ubyte val; data[start..$]) - { - if(i%perLine == 0) - res ~= format("\n 0x%-4x: ", i+start); - res ~= format("%-4x", val); - } - return res; - } - - void debugPrint() - { - writefln(debugString()); - } - // Jump to given position void jump(int newPos) { @@ -107,27 +70,12 @@ struct CodeStream return pos-data.ptr; } - // Get the current line - int getLine() - { - // call shared.linespec.findLine - return findLine(lines, cmdPos-data.ptr); - } - ubyte get() { if(len--) return *(pos++); eos("get"); } - // Used for getting an instruction. It stores the offset which can - // be used to infer the line number later. - ubyte getCmd() - { - cmdPos = pos; - return get(); - } - int getInt() { len -= 4; diff --git a/monster/vm/fstack.d b/monster/vm/fstack.d index 681e29ad7..d01ca374f 100644 --- a/monster/vm/fstack.d +++ b/monster/vm/fstack.d @@ -30,21 +30,15 @@ import monster.vm.stack; import monster.vm.error; import monster.compiler.states; import monster.compiler.functions; +import monster.compiler.linespec; // "friendly" parameter and stack handling. enum SPType { Function, // A function (script or native) + Idle, // Idle function State, // State code NConst, // Native constructor - - // The idle function callbacks are split because they handle the - // stack differently. We probably don't need to have one type for - // each though. - Idle_Initiate, // IdleFunction.initiate() - Idle_Reentry, // IdleFunction.reentry() - Idle_Abort, // IdleFunction.abort() - Idle_Check // IdleFunction.hasFinished() } // One entry in the function stack @@ -61,15 +55,46 @@ struct StackPoint SPType ftype; MonsterObject *obj; // "this"-pointer for the function - MonsterClass cls; // class owning the function - int afterStack; // Where the stack should be when this function - // returns + // 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 + // Get the class owning the function + MonsterClass getCls() + { + assert(isFunc || isState); + assert(func !is null); + return func.owner; + } + bool isStatic() + { return isFunc() && func.isStatic; } + + bool isFunc() + { return (ftype == SPType.Function) || (ftype == SPType.Idle); } + + bool isState() + { return ftype == SPType.State; } + + // Get the current source position (file name and line + // number). Mostly used for error messages. + Floc getFloc() { - return (ftype == SPType.Function) && func.isStatic; + assert(isFunc || isState); + + Floc fl; + fl.fname = getCls().name.loc.fname; + + // Subtract one to make sure we get the last instruction executed, + // not the next. + int pos = code.getPos() - 1; + if(pos < 0) pos = 0; + + fl.line = findLine(func.lines, pos); + + return fl; } } @@ -123,11 +148,12 @@ struct FunctionStack push(obj); cur.ftype = SPType.Function; cur.func = func; - cur.cls = func.owner; + + 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, func.lines); + cur.code.setData(func.bcode); assert(!func.isIdle, "don't use fstack.push() on idle functions"); } @@ -143,10 +169,9 @@ struct FunctionStack assert(obj !is null); assert(st.owner.parentOf(obj.cls)); - cur.cls = st.owner; // Set up the byte code - cur.code.setData(st.bcode, st.lines); + cur.code.setData(st.bcode); } // Native constructor @@ -157,33 +182,16 @@ struct FunctionStack cur.ftype = SPType.NConst; } - private void pushIdleCommon(Function *fn, MonsterObject *obj, SPType tp) + void pushIdle(Function *fn, MonsterObject *obj) { - // Not really needed - we will allow static idle functions later - // on. - assert(obj !is null); - push(obj); cur.func = fn; - cur.cls = fn.owner; + cur.ftype = SPType.Idle; + + assert(obj is null || fn.owner.parentOf(obj.cls)); assert(fn.isIdle, fn.name.str ~ "() is not an idle function"); - cur.ftype = tp; } - // These are used for the various idle callbacks. TODO: Probably - // overkill to have one for each, but leave it until you're sure. - void pushIdleInit(Function *fn, MonsterObject *obj) - { pushIdleCommon(fn, obj, SPType.Idle_Initiate); } - - void pushIdleReentry(Function *fn, MonsterObject *obj) - { pushIdleCommon(fn, obj, SPType.Idle_Reentry); } - - void pushIdleAbort(Function *fn, MonsterObject *obj) - { pushIdleCommon(fn, obj, SPType.Idle_Abort); } - - void pushIdleCheck(Function *fn, MonsterObject *obj) - { pushIdleCommon(fn, obj, SPType.Idle_Check); } - // Pops one entry of the stack. Checks that the stack level has been // returned to the correct position. void pop() diff --git a/monster/vm/mobject.d b/monster/vm/mobject.d index 515ec6a3f..274eb11eb 100644 --- a/monster/vm/mobject.d +++ b/monster/vm/mobject.d @@ -314,10 +314,6 @@ struct MonsterObject // don't do anything. else if(label is null) return; - // TODO: We can reorganize the entire function to deal with one - // sthread !is null test. Just do the label-checking first, and - // store the label offset - // Do we already have a thread? if(sthread !is null) { @@ -345,10 +341,9 @@ struct MonsterObject "' is not part of class " ~ cls.getName()); if(label is null) - // findLabel will return null if the label is not found. - // TODO: The begin label should probably be cached within - // State. - label = st.findLabel("begin"); + // Use the 'begin:' label, if any. It will be null there's + // no begin label. + label = st.begin; if(label !is null) { @@ -363,7 +358,7 @@ struct MonsterObject } // Don't leave an unused thread dangling - kill it instead. - if(sthread !is null && !sthread.isScheduled) + if(sthread !is null && sthread.isUnused) { sthread.kill(); sthread = null; diff --git a/monster/vm/stack.d b/monster/vm/stack.d index 8ef522e94..1802b867e 100644 --- a/monster/vm/stack.d +++ b/monster/vm/stack.d @@ -33,16 +33,13 @@ 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 +// Stack. There's only one global instance, but threads will make +// copies when they need it. CodeStack stack; -void initStack() -{ - stack.init(); -} - // A simple stack frame. All data are in chunks of 4 bytes struct CodeStack { @@ -71,8 +68,7 @@ struct CodeStack frame = null; } - // Get the current position index. Used mostly for debugging and - // error checking. + // Get the current position index. int getPos() { return total-left; @@ -110,7 +106,11 @@ struct CodeStack left = total; pos = data.ptr; - assert(fleft == left); + if(fleft != 0) + writefln("left=%s total=%s fleft=%s", left, total, fleft); + assert(frame is null); + assert(fleft == 0); + assert(fstack.isEmpty); } void pushInt(int i) @@ -174,6 +174,7 @@ struct CodeStack assert(len > 0); int[] r = getInts(len-1, len); pop(len); + assert(r.length == len); return r; } diff --git a/monster/vm/thread.d b/monster/vm/thread.d index 58ebcd0b9..1b932e7fb 100644 --- a/monster/vm/thread.d +++ b/monster/vm/thread.d @@ -81,6 +81,21 @@ struct Thread // The contents of idleObj'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; + + /******************************************************* + * * + * Private variables * + * * + *******************************************************/ + + 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 @@ -92,12 +107,45 @@ struct Thread NodeList * list; // List owning this thread int retPos; // Return position in byte code. - bool isActive; // 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 stateChange; // Set to true when a state change is in - // progress. Only used when state is changed from - // within a function in active code. + // Stored copy of the stack. Used when the thread is not running. + int[] sstack; + + + public: + /******************************************************* + * * + * Public functions * + * * + *******************************************************/ + + // Get a new thread. It starts in the 'unused' list. + static Thread* getNew(MonsterObject *obj = null) + { + auto cn = scheduler.unused.getNew(); + cn.list = &scheduler.unused; + + with(*cn) + { + theObj = obj; + + // Initialize other variables + idle = null; + idleObj = null; + isActive = false; + stateChange = false; + retPos = -1; + 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 @@ -106,7 +154,7 @@ struct Thread { if(idle !is null) { - fstack.pushIdleAbort(idle, idleObj); + fstack.pushIdle(idle, idleObj); idle.idleFunc.abort(this); fstack.pop(); idle = null; @@ -117,40 +165,46 @@ struct Thread assert(!isScheduled); } - static Thread* getNew(MonsterObject *obj = null) - { - auto cn = scheduler.unused.getNew(); - cn.list = &scheduler.unused; - cn.initialize(obj); - return cn; - } - // Remove the thread comletely void kill() { + /* + if(theObj is null) + writefln("Killing non-state thread"); + else + writefln("Killing state thread"); + */ + cancel(); list.remove(this); list = null; + + 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); + */ } + bool isDead() { return list is null; } + // Schedule this thread to run next frame - void schedule(uint offs) + void schedule(int offs) { assert(!isScheduled, "cannot schedule an already scheduled thread"); retPos = offs; + assert(offs >= 0); moveTo(scheduler.runNext); } - // Move this node to another list. - void moveTo(NodeList *to) - { - assert(list !is null); - list.moveTo(*to, this); - list = to; - } - // Are we currently scheduled? bool isScheduled() { @@ -180,24 +234,6 @@ struct Thread ( cast(NodeList.TList.Iterator)this ).getNext(); } - /******************************************************* - * * - * Public functions * - * * - *******************************************************/ - - void initialize(MonsterObject *obj) - { - theObj = obj; - - // Initialize other variables - idle = null; - idleObj = null; - isActive = false; - stateChange = false; - retPos = -1; - } - // Reenter this thread to the point where it was previously stopped. void reenter() { @@ -210,7 +246,7 @@ struct Thread assert(!isActive, "reenter cannot be called when object is already active"); assert(fstack.isEmpty, - "state code can only run at the bottom of the function stack"); + "can only reenter at the bottom of the function stack"); assert(isScheduled); if(isIdle) @@ -219,7 +255,7 @@ struct Thread assert(idleObj !is null || idle.isStatic); // Tell the idle function that we we are reentering - fstack.pushIdleReentry(idle, idleObj); + fstack.pushIdle(idle, idleObj); idle.idleFunc.reentry(this); fstack.pop(); @@ -227,9 +263,6 @@ struct Thread idle = null; } - // Remove the current node from the run list - moveTo(&scheduler.unused); - // Set the active flat to indicate that we are now actively // running. (Might not be needed in the future) isActive = true; @@ -238,6 +271,12 @@ struct Thread assert(cthread is null); cthread = this; + // 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); @@ -251,6 +290,8 @@ struct Thread // Reset the thread cthread = null; + fstack.pop(); + // We are no longer active isActive = false; @@ -258,8 +299,36 @@ struct Thread format("Stack not returned to zero after state code, __STACK__=", stack.getPos)); - fstack.pop(); + 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 + // stack. + void acquireStack() + { + assert(!isUnused(), + "unused threads should never need to aquire the stack"); + assert(sstack.length == 0, + "Thread already has a stack"); + assert(fstack.isEmpty); + + // This can be optimized later + int len = stack.getPos(); + if(len) + { + writefln("acquiring %s ints", len); + + // Get a new buffer, and copy the stack + sstack = Buffers.getInt(len); + sstack[] = stack.popInts(len); + } + stack.reset(); } private: @@ -269,21 +338,40 @@ struct Thread * * *******************************************************/ - void fail(char[] msg) + void restoreStack() { - int line = -1; - char[] file; - if(fstack.cur !is null) + assert(stack.getPos() == 0, + "cannot restore into a non-empty stack"); + + if(sstack.length) { - line = fstack.cur.code.getLine(); - file = fstack.cur.cls.name.loc.fname; + // 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) + { + Floc fl; + if(fstack.cur !is null) + fl = fstack.cur.getFloc(); - .fail(msg, file, line); + .fail(msg, fl); } - // Parse the BC.CallIdle instruction parameters and call schedule - // the given idle function. + // Parse the BC.CallIdle instruction parameters and schedule the + // given idle function. void callIdle(MonsterObject *iObj) { assert(isActive && fstack.isStateCode, @@ -316,7 +404,7 @@ struct Thread extraData = *idleObj.getExtra(idle.owner); // Notify the idle function - fstack.pushIdleInit(idle, idleObj); + fstack.pushIdle(idle, idleObj); if(idle.idleFunc.initiate(this)) moveTo(&scheduler.wait); fstack.pop(); @@ -365,13 +453,11 @@ struct Thread // Get some values from the function stack CodeStream *code = &fstack.cur.code; MonsterObject *obj = fstack.cur.obj; - MonsterClass cls = fstack.cur.cls; - int clsInd = cls.getTreeIndex(); + MonsterClass cls = fstack.cur.getCls(); // Only an object belonging to this thread can be passed to // execute() on the function stack. assert(obj is null || cls.parentOf(obj)); - assert(obj is null || obj.cls.upcast(cls) == clsInd); assert(obj !is null || fstack.cur.isStatic); // Pops a pointer off the stack. Null pointers will throw an @@ -392,7 +478,7 @@ struct Thread // Variable in this object if(type == PT.DataOffs) - return obj.getDataInt(clsInd, index); + return obj.getDataInt(cls.treeIndex, index); // This object, but another (parent) class if(type == PT.DataOffsCls) @@ -449,7 +535,7 @@ struct Thread //for(long i=0;i