From 6dcf6c565afa65cc3d85b2e90b7b74e2bfbccdff Mon Sep 17 00:00:00 2001 From: nkorslund Date: Thu, 22 Jan 2009 20:36:36 +0000 Subject: [PATCH] Update to latest Monster, and minor simplification of the scripts. git-svn-id: https://openmw.svn.sourceforge.net/svnroot/openmw/trunk@83 ea6a568a-9f4f-0410-981a-c910a81bb256 --- core/resource.d | 6 -- esm/loadlevlist.d | 6 +- esmtool.d | 2 +- monster/compiler/functions.d | 4 +- monster/compiler/scopes.d | 7 ++ monster/modules/all.d | 4 +- monster/modules/random.d | 49 +++++++++ monster/modules/random.mn | 8 ++ monster/modules/threads.d | 72 +++++--------- monster/util/freelist.d | 5 +- monster/vm/fstack.d | 15 ++- monster/vm/mobject.d | 40 ++++++++ monster/vm/stack.d | 7 +- monster/vm/thread.d | 18 ++++ mscripts/config.mn | 2 +- mscripts/fpsticker.mn | 4 +- mscripts/gameobjects/gameobject.mn | 2 +- mscripts/gmst.mn | 2 +- mscripts/object.mn | 30 ------ mscripts/{object.d => setup.d} | 17 +--- mscripts/sound/jukebox.mn | 2 +- mscripts/sound/music.mn | 2 +- mscripts/test.mn | 2 - openmw.d | 2 +- util/random.d | 155 ----------------------------- 25 files changed, 187 insertions(+), 276 deletions(-) create mode 100644 monster/modules/random.d create mode 100644 monster/modules/random.mn delete mode 100644 mscripts/object.mn rename mscripts/{object.d => setup.d} (81%) delete mode 100644 util/random.d diff --git a/core/resource.d b/core/resource.d index abe1491975..b723308090 100644 --- a/core/resource.d +++ b/core/resource.d @@ -31,7 +31,6 @@ import std.path; import monster.util.aa; import monster.util.string; -import util.random; import bsa.bsafile; @@ -51,9 +50,6 @@ import nif.nif; import core.filefinder; //import core.config; -// Random number generator -DRand rnd; - // These are handles for various resources. They may refer to a file // in the file system, an entry in a BSA archive, or point to an // already loaded resource. Resource handles that are not implemented @@ -129,8 +125,6 @@ struct ResourceManager void initResources() { - rnd = new DRand; - bsa = new FileFinder(config.bsaDir, "bsa", Recurse.No); archives.length = bsa.length; foreach(int i, ref BSAFile f; archives) diff --git a/esm/loadlevlist.d b/esm/loadlevlist.d index 129343e66a..fc2a09feec 100644 --- a/esm/loadlevlist.d +++ b/esm/loadlevlist.d @@ -25,7 +25,7 @@ module esm.loadlevlist; import esm.imports; import esm.loadcrea; -import util.random; +import monster.modules.random : randInt; /* * Leveled lists. Since these have identical layout, I only bothered @@ -103,7 +103,7 @@ struct LeveledListT(bool creature) // TODO: Find out if this is indeed correct. // Test if no creature is to be selected - if(rnd.randInt(0, 255) < chanceNone) return -1; + if(randInt(0, 255) < chanceNone) return -1; // Find the highest item below or equal to the Player level for(i=list.length-1; i>=0; i--) @@ -126,7 +126,7 @@ struct LeveledListT(bool creature) } // Select a random item - return rnd.randInt(bottom, top); + return randInt(bottom, top); } void load() diff --git a/esmtool.d b/esmtool.d index 5465ce325b..a43361803d 100644 --- a/esmtool.d +++ b/esmtool.d @@ -28,7 +28,7 @@ import std.stdio; import core.memory; import esm.esmmain; import monster.util.string; -import mscripts.object; +import mscripts.setup; import std.gc; import gcstats; diff --git a/monster/compiler/functions.d b/monster/compiler/functions.d index 4a8f315eef..228c0c6b7f 100644 --- a/monster/compiler/functions.d +++ b/monster/compiler/functions.d @@ -274,6 +274,8 @@ struct Function auto fd = new FuncDeclaration; // Parse and comile the function fd.parseFile(tokens, this); + name.str = file; + name.loc.fname = file; fd.resolve(mc.sc); fd.resolveBody(); fd.compile(); @@ -287,7 +289,7 @@ struct Function private: - static const char[] int_class = "class _Func_Internal_;"; + static const char[] int_class = "class _ScriptFile_;"; static MonsterClass int_mc; static MonsterObject *int_mo; } diff --git a/monster/compiler/scopes.d b/monster/compiler/scopes.d index 339eafed50..c6210a7b65 100644 --- a/monster/compiler/scopes.d +++ b/monster/compiler/scopes.d @@ -338,6 +338,13 @@ abstract class Scope void registerImport(MonsterClass mc) { registerImport(new ImportHolder(mc)); } + // Even more user-friendly version. Takes a list of class names. + void registerImport(char[][] cls ...) + { + foreach(c; cls) + registerImport(MonsterClass.find(c)); + } + // Used for summing up stack level. Redeclared in StackScope. int getTotLocals() { return 0; } int getLocals() { assert(0); } diff --git a/monster/modules/all.d b/monster/modules/all.d index 4f364f4104..9dab2d0d4e 100644 --- a/monster/modules/all.d +++ b/monster/modules/all.d @@ -3,6 +3,7 @@ module monster.modules.all; import monster.modules.io; import monster.modules.timer; import monster.modules.frames; +import monster.modules.random; import monster.modules.threads; void initAllModules() @@ -10,5 +11,6 @@ void initAllModules() initIOModule(); initTimerModule(); initFramesModule(); - initThreadModule; + initThreadModule(); + initRandomModule(); } diff --git a/monster/modules/random.d b/monster/modules/random.d new file mode 100644 index 0000000000..6d0173e7ac --- /dev/null +++ b/monster/modules/random.d @@ -0,0 +1,49 @@ + +// This module provides simple random number generation. Since this is +// intended for game development, speed and simplicity is favored over +// flexibility and random number quality. +module monster.modules.random; + +import monster.monster; +import std.random; + +const char[] moduleDef = +"module random; + +native uint rand(); // Return a number between 0 and uint.max, inclusive +native float frand(); // Return a number between 0 and 1, inclusive + +// Return a random number between a and b, inclusive. Allows negative +// numbers, and works with a>b, a= a) assert( (a <= result) && (result <= b) ); + else if(a > b) assert( (b <= result) && (result <= a) ); +} +body +{ + if(a>b) return cast(int)(rand() % (a-b+1)) + b; + else if(b>a) return cast(int)(rand() % (b-a+1)) + a; + else return a; +} + +void initRandomModule() +{ + static MonsterClass mc; + if(mc !is null) return; + + mc = new MonsterClass(MC.String, moduleDef, "random"); + + mc.bind("rand", { stack.pushInt(rand()); }); + mc.bind("frand", { stack.pushFloat(rand()*_frandFactor); }); + mc.bind("randInt", { stack.pushInt(randInt(stack.popInt, + stack.popInt)); }); +} diff --git a/monster/modules/random.mn b/monster/modules/random.mn new file mode 100644 index 0000000000..273df93a80 --- /dev/null +++ b/monster/modules/random.mn @@ -0,0 +1,8 @@ +module random; + +native uint rand(); // Return a number between 0 and uint.max, inclusive +native float frand(); // Return a number between 0 and 1, inclusive + +// Return a random number between a and b, inclusive. Allows negative +// numbers, and works with a>b, a 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(); + auto trd = mo.thread(name); 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 +// Call is used to restore a thread that was previously paused. It +// will enter the thread immediately, like a normal function call, but +// it will still run in its own thread. If you only wish to schedule +// it for later, use restart instead. +class Call : IdleFunction { override: IS initiate(Thread *t) { if(params.obj is trdSing) - fail("Cannot use resume() on our own thread."); + fail("Cannot use call() 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."); + fail("Cannot use call() on our own thread."); if(trd.isDead) - fail("Cannot resume a dead thread."); + fail("Cannot call a dead thread."); if(!trd.isPaused) - fail("Can only use resume() on paused threads"); + fail("Can only use call() on paused threads"); // Background the current thread. Move it to the pause list // first, so background doesn't inadvertently delete it. @@ -234,18 +223,7 @@ class Wait : IdleFunction } 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); -} +{ getOwner().restart(); } void isDead() { stack.pushBool(getOwner().isDead); } @@ -270,7 +248,7 @@ void initThreadModule() trdSing = _threadClass.getSing(); _threadClass.bind("kill", new Kill); - _threadClass.bind("resume", new Resume); + _threadClass.bind("call", new Call); _threadClass.bind("pause", new Pause); _threadClass.bind("wait", new Wait); diff --git a/monster/util/freelist.d b/monster/util/freelist.d index 2cfc555838..b41c9f9c99 100644 --- a/monster/util/freelist.d +++ b/monster/util/freelist.d @@ -175,8 +175,6 @@ struct BufferList(int size) void free(void* p) { remove(cast(ValuePtr)p); } } -import std.stdio; - struct Buffers { static: @@ -204,8 +202,7 @@ struct Buffers // Too large for our lists - just use malloc else { - writefln("WARNING: using malloc for %s ints (%s bytes)", - size, size*int.sizeof); + //writefln("WARNING: using malloc for %s ints (%s bytes)", size, size*int.sizeof); return ( cast(int*)malloc(size*int.sizeof) )[0..size]; } } diff --git a/monster/vm/fstack.d b/monster/vm/fstack.d index 2136155d1f..4b498e122c 100644 --- a/monster/vm/fstack.d +++ b/monster/vm/fstack.d @@ -131,7 +131,9 @@ struct StackPoint name = func.name.str; if(isIdle) type = "idle"; - else type = "function"; + else if(isNormal) type = "script"; + else if(isNative) type = "native"; + else assert(0); } // Function location and name @@ -322,8 +324,17 @@ struct FunctionStack { char[] res; + int i; foreach(ref c; list) - res = c.toString ~ '\n' ~ res; + { + char[] msg; + if(i == 0) + msg = " (<---- current function)"; + else if(i == list.length-1) + msg = " (<---- start of function stack)"; + res = c.toString ~ msg ~ '\n' ~ res; + i++; + } return "Trace:\n" ~ res; } diff --git a/monster/vm/mobject.d b/monster/vm/mobject.d index af9a13718b..e8e396250b 100644 --- a/monster/vm/mobject.d +++ b/monster/vm/mobject.d @@ -27,6 +27,7 @@ import monster.vm.thread; import monster.vm.error; import monster.vm.mclass; import monster.vm.arrays; +import monster.vm.stack; import monster.util.freelist; import monster.util.list; @@ -34,6 +35,7 @@ import monster.util.list; import monster.compiler.states; import monster.compiler.variables; import monster.compiler.scopes; +import monster.compiler.functions; import std.string; import std.stdio; @@ -262,6 +264,44 @@ struct MonsterObject cls.findFunction(name).call(this); } + // Create a paused thread that's set up to call the given + // function. It must be started with Thread.call() or + // Thread.restart(). + Thread *thread(char[] name) + { return thread(cls.findFunction(name)); } + Thread *thread(Function *fn) + { + assert(fn !is null); + if(fn.paramSize > 0) + fail("thread(): function " ~ fn.name.str ~ " cannot have parameters"); + + Thread *trd = Thread.getNew(); + + // Schedule the function to run the next frame + trd.pushFunc(fn, this); + assert(trd.isPaused); + assert(trd.fstack.cur !is null); + + // pushFunc will mess with the stack frame though, so fix it. + trd.fstack.cur.frame = stack.getStart(); + if(cthread !is null) + cthread.fstack.restoreFrame(); + + return trd; + } + + // Create a thread containing the function and schedule it to start + // the next frame + Thread *start(char[] name) + { return start(cls.findFunction(name)); } + Thread *start(Function *fn) + { + assert(fn !is null); + auto trd = thread(fn); + trd.restart(); + return trd; + } + // Call a function non-virtually. In other words, ignore // derived objects. void nvcall(char[] name) diff --git a/monster/vm/stack.d b/monster/vm/stack.d index cf0ed9f264..8a872b2481 100644 --- a/monster/vm/stack.d +++ b/monster/vm/stack.d @@ -139,12 +139,9 @@ struct CodeStack } // Get the pointer from the start of the stack - int *getStartInt(int pos) + int *getStart() { - if(pos < 0 || pos >= getPos) - fail("CodeStack.getStartInt() pointer out of range"); - - return &data[pos]; + return cast(int*)data.ptr; } // Get the pointer to an int at the given position backwards from diff --git a/monster/vm/thread.d b/monster/vm/thread.d index b4eaea3610..d80f452711 100644 --- a/monster/vm/thread.d +++ b/monster/vm/thread.d @@ -124,6 +124,20 @@ struct Thread return cn; } + // Schedule the function to run the next frame. Can only be used on + // paused threads. + void restart() + { + if(isDead) + fail("Cannot restart a dead thread"); + + if(!isPaused) + fail("Can only use restart() on paused threads"); + + // Move to the runlist + moveTo(scheduler.runNext); + } + // Stop the thread and return it to the freelist void kill() { @@ -238,6 +252,7 @@ struct Thread bool isTransient() { return list is &scheduler.transient; } bool isRunning() { return cthread is this; } bool isDead() { return list is null; } + bool isAlive() { return !isDead; } bool isPaused() { return list is &scheduler.paused; } // Get the next node in the freelist @@ -293,6 +308,7 @@ struct Thread // Background the thread background(); + assert(cthread is null); } // Put this thread in the background. Acquires the stack and @@ -401,6 +417,8 @@ struct Thread if(fstack.cur !is null) fl = fstack.cur.getFloc(); + msg ~= '\n' ~ fstack.toString(); + .fail(msg, fl); } diff --git a/mscripts/config.mn b/mscripts/config.mn index a1d6a057bb..1973750f90 100644 --- a/mscripts/config.mn +++ b/mscripts/config.mn @@ -21,7 +21,7 @@ */ -singleton Config : Object; +singleton Config; // Only some config options have been moved into Monster. Key bindings // and other low-level settings are still handled in D. diff --git a/mscripts/fpsticker.mn b/mscripts/fpsticker.mn index 824199f28f..ea69abcc94 100644 --- a/mscripts/fpsticker.mn +++ b/mscripts/fpsticker.mn @@ -1,12 +1,14 @@ // Small script that prints the FPS to screen with regular intervals. -import io, timer, frames; +import frames; // 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); +// counter and totalTime (in the 'frames' module) are updated +// automatically by the system. ulong lastFrame = counter; float lastTime = totalTime; diff --git a/mscripts/gameobjects/gameobject.mn b/mscripts/gameobjects/gameobject.mn index 1c81713cf3..350d9aae67 100644 --- a/mscripts/gameobjects/gameobject.mn +++ b/mscripts/gameobjects/gameobject.mn @@ -23,7 +23,7 @@ // An object that exists inside a cell. All cell objects must have a // position in space. -class GameObject : Object; +class GameObject; // Is this object placed in a cell? isPlaced is true if the object is // displayed inside a cell with a mesh and given 3D coordinates, and diff --git a/mscripts/gmst.mn b/mscripts/gmst.mn index 0d2e4b2de0..5a5f0defe0 100644 --- a/mscripts/gmst.mn +++ b/mscripts/gmst.mn @@ -24,7 +24,7 @@ // Contains all the game settings (GMST) variables of Morrowind, // Tribunal and Bloodmoon. Based on "Morrowind Scripting for Dummies" // (v9). -singleton GMST : Object; +singleton GMST; // Most of the comments are copied from MSfD. A bit of cleanup is // still needed. diff --git a/mscripts/object.mn b/mscripts/object.mn deleted file mode 100644 index 7bdf3e2b34..0000000000 --- a/mscripts/object.mn +++ /dev/null @@ -1,30 +0,0 @@ -/* - OpenMW - The completely unofficial reimplementation of Morrowind - Copyright (C) 2008 Nicolay Korslund - Email: < korslund@gmail.com > - WWW: http://openmw.snaptoad.com/ - - This file (object.mn) is part of the OpenMW package. - - OpenMW is distributed as free software: you can redistribute it - and/or modify it under the terms of the GNU General Public License - version 3, as published by the Free Software Foundation. - - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - version 3 along with this program. If not, see - http://www.gnu.org/licenses/ . - - */ - -// This is the base class of all OpenMW Monster classes. -class Object; - -import io, timer; - -// Get a random number between a and b (inclusive) -native int randInt(int a, int b); diff --git a/mscripts/object.d b/mscripts/setup.d similarity index 81% rename from mscripts/object.d rename to mscripts/setup.d index 8a2b2d7e35..70a0f5ae95 100644 --- a/mscripts/object.d +++ b/mscripts/setup.d @@ -21,16 +21,13 @@ */ -module mscripts.object; +module mscripts.setup; import monster.monster; +import monster.compiler.scopes : global; import monster.modules.timer; -import std.stdio; -import std.date; -import core.resource : rnd; import core.config; -import sound.music; // Set up the base Monster classes we need in OpenMW void initMonsterScripts() @@ -43,17 +40,13 @@ void initMonsterScripts() vm.addPath("mscripts/gameobjects/"); vm.addPath("mscripts/sound/"); - // Make sure the Object class is loaded - auto mc = new MonsterClass("Object", "object.mn"); + // Import some modules into the global scope, so we won't have to + // import them manually in each script. + global.registerImport("io", "random", "timer"); // Get the Config singleton object config.mo = (new MonsterClass("Config")).getSing(); - // Bind various functions - mc.bind("randInt", - { stack.pushInt(rnd.randInt - (stack.popInt,stack.popInt));}); - // Run the fps ticker vm.run("fpsticker.mn"); diff --git a/mscripts/sound/jukebox.mn b/mscripts/sound/jukebox.mn index 0900d5c451..c8acfedd3d 100644 --- a/mscripts/sound/jukebox.mn +++ b/mscripts/sound/jukebox.mn @@ -26,7 +26,7 @@ fade in and fade out, and adjust volume. */ -class Jukebox : Object; +class Jukebox; // Between 0 (off) and 1 (full volume) float fadeLevel = 0.0; diff --git a/mscripts/sound/music.mn b/mscripts/sound/music.mn index 30f0ed8d49..1bdbe980af 100644 --- a/mscripts/sound/music.mn +++ b/mscripts/sound/music.mn @@ -22,7 +22,7 @@ */ // This class controls all the music. -singleton Music : Object; +singleton Music; // Create one jukebox for normal music, and one for battle music. This // way we can pause / fade out one while the other resumes / fades in. diff --git a/mscripts/test.mn b/mscripts/test.mn index 7fde6ff2e2..4b740fc436 100644 --- a/mscripts/test.mn +++ b/mscripts/test.mn @@ -1,7 +1,5 @@ // A short example script -import io, timer; - sleep(6); while(true) diff --git a/openmw.d b/openmw.d index ca8f7b3bf6..f81effb245 100644 --- a/openmw.d +++ b/openmw.d @@ -43,7 +43,7 @@ import core.config; import monster.util.string; import monster.vm.mclass; -import mscripts.object; +import mscripts.setup; import sound.audio; diff --git a/util/random.d b/util/random.d deleted file mode 100644 index 10685118a2..0000000000 --- a/util/random.d +++ /dev/null @@ -1,155 +0,0 @@ -/* - OpenMW - The completely unofficial reimplementation of Morrowind - Copyright (C) 2008 Nicolay Korslund - Email: < korslund@gmail.com > - WWW: http://openmw.snaptoad.com/ - - This file (random.d) is part of the OpenMW package. - - OpenMW is distributed as free software: you can redistribute it - and/or modify it under the terms of the GNU General Public License - version 3, as published by the Free Software Foundation. - - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - version 3 along with this program. If not, see - http://www.gnu.org/licenses/ . - - */ - -module util.random; - -private import std.date; -private import std.random; - -abstract class Random -{ - static const double scale = 1.0/uint.max; - - // Initialize from current time - this() { initialize(); } - - // Initialize from parameter - this(long seed) { initialize(seed); } - - // Reinitialize with seed - abstract void initialize(long newSeed); - - // Produce random numbers between 0 and uint.max - abstract uint rand(); - - // Default is to initialize using current time as seed - void initialize() { initialize(getUTCtime()); } - - // Produce a uniform random number between 0 and 1 - double random() - { - return rand() * scale; - } - - // Return a uniform random number between a and b. Works for the - // both the cases a < b and a > b. - double random(double a, double b) - in - { - // We only disallow nan parameters - assert(a <>= b); - } - out(result) - { - // Result must be in range m < result < M, where m=min(a,b) and M=max(a,b) - if(b > a) assert( (a < result) && (result < b) ); - else if(a > b) assert( (b < result) && (result < a) ); - } - body - { return random()*(b - a) + a; } - - // Return a random integer between a and b, inclusive. - int randInt(int a, int b) - out(result) - { - // Result must be in range m <= result <= M, where m=min(a,b) and M=max(a,b) - if(b >= a) assert( (a <= result) && (result <= b) ); - else if(a > b) assert( (b <= result) && (result <= a) ); - } - body - { - if(a>b) return cast(int)(rand() % (a-b+1)) + b; - else if(b>a) return cast(int)(rand() % (b-a+1)) + a; - else return a; - } - - // Allow using "function call" syntax: - // - // Random ran = new Random1; - // double d = ran(); // Calls ran.random() - // d = ran(a,b); // Calls ran.random(a,b); - double opCall() { return random(); } - double opCall(double a, double b) { return random(a, b); } - - // Return the seed originally given the object - long getSeed() {return origSeed;} - -protected: - long origSeed; // Value used to seed the generator -} - -// Uses the standard library generator -class DRand : Random -{ - // Initialize from current time - this() { super(); } - - // Initialize from parameter - this(long seed) { super(seed); } - - uint rand() { return std.random.rand(); } - - void initialize(long newSeed) - { - origSeed = newSeed; - rand_seed(cast(uint)newSeed, 0); - } - - alias Random.initialize initialize; - - unittest - { - struct tmp { import std.stdio; } - alias tmp.writef writef; - alias tmp.writefln writefln; - - writefln("Unittest for class DRand"); - - DRand ran = new DRand; - writefln("Seed (from time) = ", ran.getSeed()); - - // Take a look at some numbers on screen - writefln("Some random numbers in [0,1]:"); - for(int i=0; i<10; i++) - writef(" ", ran()); - - ran = new DRand(0); - writefln("\nNew seed (preset) = ", ran.getSeed()); - - // Take a look at some numbers on screen - writefln("Some random numbers in [0,1]:"); - for(int i=0; i<10; i++) - writef(" ", ran()); - - // Check that all interfaces work (compile time) - ran(); - ran(1,2); - ran.random(); - ran.random(3,4); - ran.initialize(); - ran.initialize(10); - ran.randInt(-3,5); - - writefln("\nEnd of unittest for class DRand\n"); - } -}