summaryrefslogtreecommitdiff
path: root/src/scripting
diff options
context:
space:
mode:
authorThorbjørn Lindeijer <thorbjorn@lindeijer.nl>2012-03-09 21:30:42 +0100
committerThorbjørn Lindeijer <thorbjorn@lindeijer.nl>2012-03-10 18:07:29 +0100
commit78c912fb4007c3e5f0b43de02646772acb21ecf2 (patch)
treee45409ff061de75b0e4273763a87f5a25de6a65b /src/scripting
parent2fa7d1f39b24714ee6dc72b6b9e61ec5a1997724 (diff)
downloadmanaserv-78c912fb4007c3e5f0b43de02646772acb21ecf2.tar.gz
manaserv-78c912fb4007c3e5f0b43de02646772acb21ecf2.tar.bz2
manaserv-78c912fb4007c3e5f0b43de02646772acb21ecf2.tar.xz
manaserv-78c912fb4007c3e5f0b43de02646772acb21ecf2.zip
Moved the managing of NPC script coroutines into C++
Rather than wrapping NPC functions up in coroutines in the Lua side, they are now managed on the C++ side as "script threads", which are essentially the same thing. The main purpose is that the server can now know whether any of these long running script interactions are still active, which will probably be useful when adding the ability to reload scripts. Reviewed-by: Erik Schilling
Diffstat (limited to 'src/scripting')
-rw-r--r--src/scripting/lua.cpp137
-rw-r--r--src/scripting/luascript.cpp183
-rw-r--r--src/scripting/luascript.h40
-rw-r--r--src/scripting/luautil.cpp12
-rw-r--r--src/scripting/luautil.h5
-rw-r--r--src/scripting/script.cpp42
-rw-r--r--src/scripting/script.h76
7 files changed, 341 insertions, 154 deletions
diff --git a/src/scripting/lua.cpp b/src/scripting/lua.cpp
index 540925b1..5053070a 100644
--- a/src/scripting/lua.cpp
+++ b/src/scripting/lua.cpp
@@ -91,20 +91,6 @@ static int on_character_death_accept(lua_State *s)
return 0;
}
-static int on_npc_quest_reply(lua_State *s)
-{
- luaL_checktype(s, 1, LUA_TFUNCTION);
- LuaScript::setQuestReplyCallback(getScript(s));
- return 0;
-}
-
-static int on_npc_post_reply(lua_State *s)
-{
- luaL_checktype(s, 1, LUA_TFUNCTION);
- LuaScript::setPostReplyCallback(getScript(s));
- return 0;
-}
-
static int on_being_death(lua_State *s)
{
luaL_checktype(s, 1, LUA_TFUNCTION);
@@ -133,34 +119,6 @@ static int on_npc_start(lua_State *s)
return 0;
}
-static int on_npc_next(lua_State *s)
-{
- luaL_checktype(s, 1, LUA_TFUNCTION);
- NPC::setNextCallback(getScript(s));
- return 0;
-}
-
-static int on_npc_choose(lua_State *s)
-{
- luaL_checktype(s, 1, LUA_TFUNCTION);
- NPC::setChooseCallback(getScript(s));
- return 0;
-}
-
-static int on_npc_integer(lua_State *s)
-{
- luaL_checktype(s, 1, LUA_TFUNCTION);
- NPC::setIntegerCallback(getScript(s));
- return 0;
-}
-
-static int on_npc_string(lua_State *s)
-{
- luaL_checktype(s, 1, LUA_TFUNCTION);
- NPC::setStringCallback(getScript(s));
- return 0;
-}
-
static int on_npc_update(lua_State *s)
{
luaL_checktype(s, 1, LUA_TFUNCTION);
@@ -233,11 +191,15 @@ static int npc_message(lua_State *s)
size_t l;
const char *m = luaL_checklstring(s, 3, &l);
+ Script::Thread *thread = checkCurrentThread(s);
+
MessageOut msg(GPMSG_NPC_MESSAGE);
msg.writeInt16(p->getPublicID());
msg.writeString(m, l);
gameHandler->sendTo(q, msg);
- return 0;
+
+ thread->mState = Script::ThreadPaused;
+ return lua_yield(s, 0);
}
/**
@@ -249,6 +211,8 @@ static int npc_choice(lua_State *s)
NPC *p = checkNPC(s, 1);
Character *q = checkCharacter(s, 2);
+ Script::Thread *thread = checkCurrentThread(s);
+
MessageOut msg(GPMSG_NPC_CHOICE);
msg.writeInt16(p->getPublicID());
for (int i = 3, i_end = lua_gettop(s); i <= i_end; ++i)
@@ -282,11 +246,13 @@ static int npc_choice(lua_State *s)
}
}
gameHandler->sendTo(q, msg);
- return 0;
+
+ thread->mState = Script::ThreadExpectingNumber;
+ return lua_yield(s, 0);
}
/**
- * mana.npc_integer(NPC*, Character*, int min, int max, int defaut = min): void
+ * mana.npc_integer(NPC*, Character*, int min, int max, int default = min): void
* Callback for sending a NPC_INTEGER.
*/
static int npc_ask_integer(lua_State *s)
@@ -297,6 +263,8 @@ static int npc_ask_integer(lua_State *s)
int max = luaL_checkint(s, 4);
int defaultValue = luaL_optint(s, 5, min);
+ Script::Thread *thread = checkCurrentThread(s);
+
MessageOut msg(GPMSG_NPC_NUMBER);
msg.writeInt16(p->getPublicID());
msg.writeInt32(min);
@@ -304,7 +272,8 @@ static int npc_ask_integer(lua_State *s)
msg.writeInt32(defaultValue);
gameHandler->sendTo(q, msg);
- return 0;
+ thread->mState = Script::ThreadExpectingNumber;
+ return lua_yield(s, 0);
}
/**
@@ -316,15 +285,18 @@ static int npc_ask_string(lua_State *s)
NPC *p = checkNPC(s, 1);
Character *q = checkCharacter(s, 2);
+ Script::Thread *thread = checkCurrentThread(s);
+
MessageOut msg(GPMSG_NPC_STRING);
msg.writeInt16(p->getPublicID());
gameHandler->sendTo(q, msg);
- return 0;
+ thread->mState = Script::ThreadExpectingString;
+ return lua_yield(s, 0);
}
/**
- * mana.npc_create(string name, int id, int x, int y): NPC*
+ * mana.npc_create(string name, int id, int gender, int x, int y): NPC*
* Callback for creating a NPC on the current map with the current script.
*/
static int npc_create(lua_State *s)
@@ -335,10 +307,9 @@ static int npc_create(lua_State *s)
const int x = luaL_checkint(s, 4);
const int y = luaL_checkint(s, 5);
- Script *t = getScript(s);
- MapComposite *m = checkCurrentMap(s, t);
+ MapComposite *m = checkCurrentMap(s);
- NPC *q = new NPC(name, id, t);
+ NPC *q = new NPC(name, id);
q->setGender(getGender(gender));
q->setMap(m);
q->setPosition(Point(x, y));
@@ -514,7 +485,7 @@ static int chr_get_inventory(lua_State *s)
}
/**
- * Callback for gathering equiupment information.
+ * Callback for gathering equipment information.
* mana.chr_get_inventory(character): table[](slot, item id, name)
* Returns in the inventory slots order, the slot id, the item ids, and name.
* Only slots not empty are returned.
@@ -1295,6 +1266,8 @@ static int chr_get_quest(lua_State *s)
const char *name = luaL_checkstring(s, 2);
luaL_argcheck(s, name[0] != 0, 2, "empty variable name");
+ Script::Thread *thread = checkCurrentThread(s);
+
std::string value;
bool res = getQuestVar(q, name, value);
if (res)
@@ -1304,7 +1277,9 @@ static int chr_get_quest(lua_State *s)
}
QuestCallback f = { &LuaScript::getQuestCallback, getScript(s) };
recoverQuestVar(q, name, f);
- return 0;
+
+ thread->mState = Script::ThreadExpectingString;
+ return lua_yield(s, 0);
}
/**
@@ -1507,8 +1482,7 @@ static int get_beings_in_rectangle(lua_State *s)
int tableStackPosition = lua_gettop(s);
int tableIndex = 1;
Rectangle rect = {x, y ,w, h};
- for (BeingIterator i(
- m->getInsideRectangleIterator(rect)); i; ++i)
+ for (BeingIterator i(m->getInsideRectangleIterator(rect)); i; ++i)
{
Being *b = *i;
char t = b->getType();
@@ -1530,10 +1504,14 @@ static int chr_get_post(lua_State *s)
{
Character *c = checkCharacter(s, 1);
- PostCallback f = { &LuaScript::getPostCallback, getScript(s) };
+ Script *script = getScript(s);
+ Script::Thread *thread = checkCurrentThread(s, script);
+
+ PostCallback f = { &LuaScript::getPostCallback, script };
postMan->getPost(c, f);
- return 0;
+ thread->mState = Script::ThreadExpectingTwoStrings;
+ return lua_yield(s, 0);
}
/**
@@ -2135,31 +2113,26 @@ static int require_loader(lua_State *s)
LuaScript::LuaScript():
nbArgs(-1)
{
- mState = luaL_newstate();
- luaL_openlibs(mState);
+ mRootState = luaL_newstate();
+ mCurrentState = mRootState;
+ luaL_openlibs(mRootState);
// Register package loader that goes through the resource manager
// table.insert(package.loaders, 2, require_loader)
- lua_getglobal(mState, "package");
- lua_getfield(mState, -1, "loaders");
- lua_pushcfunction(mState, require_loader);
- lua_rawseti(mState, -2, 2);
- lua_pop(mState, 2);
+ lua_getglobal(mRootState, "package");
+ lua_getfield(mRootState, -1, "loaders");
+ lua_pushcfunction(mRootState, require_loader);
+ lua_rawseti(mRootState, -2, 2);
+ lua_pop(mRootState, 2);
// Put the callback functions in the scripting environment.
static luaL_Reg const callbacks[] = {
{ "on_character_death", &on_character_death },
{ "on_character_death_accept", &on_character_death_accept },
- { "on_npc_quest_reply", &on_npc_quest_reply },
- { "on_npc_post_reply", &on_npc_post_reply },
{ "on_being_death", &on_being_death },
{ "on_being_remove", &on_being_remove },
{ "on_update", &on_update },
{ "on_npc_start", &on_npc_start },
- { "on_npc_next", &on_npc_next },
- { "on_npc_choose", &on_npc_choose },
- { "on_npc_integer", &on_npc_integer },
- { "on_npc_string", &on_npc_string },
{ "on_npc_update", &on_npc_update },
{ "on_create_npc_delayed", &on_create_npc_delayed },
{ "on_map_initialize", &on_map_initialize },
@@ -2256,8 +2229,8 @@ LuaScript::LuaScript():
{ "announce", &announce },
{ NULL, NULL }
};
- luaL_register(mState, "mana", callbacks);
- lua_pop(mState, 1); // pop the 'mana' table
+ luaL_register(mRootState, "mana", callbacks);
+ lua_pop(mRootState, 1); // pop the 'mana' table
static luaL_Reg const members_ItemClass[] = {
{ "on", &item_class_on },
@@ -2283,20 +2256,20 @@ LuaScript::LuaScript():
{ NULL, NULL }
};
- LuaItemClass::registerType(mState, "ItemClass", members_ItemClass);
- LuaMapObject::registerType(mState, "MapObject", members_MapObject);
- LuaMonsterClass::registerType(mState, "MonsterClass", members_MonsterClass);
- LuaStatusEffect::registerType(mState, "StatusEffect", members_StatusEffect);
+ LuaItemClass::registerType(mRootState, "ItemClass", members_ItemClass);
+ LuaMapObject::registerType(mRootState, "MapObject", members_MapObject);
+ LuaMonsterClass::registerType(mRootState, "MonsterClass", members_MonsterClass);
+ LuaStatusEffect::registerType(mRootState, "StatusEffect", members_StatusEffect);
// Make script object available to callback functions.
- lua_pushlightuserdata(mState, const_cast<char *>(&registryKey));
- lua_pushlightuserdata(mState, this);
- lua_rawset(mState, LUA_REGISTRYINDEX);
+ lua_pushlightuserdata(mRootState, const_cast<char *>(&registryKey));
+ lua_pushlightuserdata(mRootState, this);
+ lua_rawset(mRootState, LUA_REGISTRYINDEX);
// Push the error handler to first index of the stack
- lua_getglobal(mState, "debug");
- lua_getfield(mState, -1, "traceback");
- lua_remove(mState, 1); // remove the 'debug' table
+ lua_getglobal(mRootState, "debug");
+ lua_getfield(mRootState, -1, "traceback");
+ lua_remove(mRootState, 1); // remove the 'debug' table
loadFile("scripts/lua/libmana.lua");
}
diff --git a/src/scripting/luascript.cpp b/src/scripting/luascript.cpp
index c9337d59..dfa64d0f 100644
--- a/src/scripting/luascript.cpp
+++ b/src/scripting/luascript.cpp
@@ -21,8 +21,8 @@
#include "luascript.h"
-
#include "scripting/luautil.h"
+#include "scripting/scriptmanager.h"
#include "game-server/character.h"
#include "utils/logger.h"
@@ -30,8 +30,6 @@
#include <cassert>
#include <cstring>
-Script::Ref LuaScript::mQuestReplyCallback;
-Script::Ref LuaScript::mPostReplyCallback;
Script::Ref LuaScript::mDeathNotificationCallback;
Script::Ref LuaScript::mRemoveNotificationCallback;
@@ -39,36 +37,59 @@ const char LuaScript::registryKey = 0;
LuaScript::~LuaScript()
{
- lua_close(mState);
+ lua_close(mRootState);
}
void LuaScript::prepare(Ref function)
{
assert(nbArgs == -1);
+
assert(function.isValid());
- lua_rawgeti(mState, LUA_REGISTRYINDEX, function.value);
- assert(lua_isfunction(mState, -1));
+ lua_rawgeti(mCurrentState, LUA_REGISTRYINDEX, function.value);
+ assert(lua_isfunction(mCurrentState, -1));
+ nbArgs = 0;
+}
+
+Script::Thread *LuaScript::newThread()
+{
+ assert(nbArgs == -1);
+ assert(!mCurrentThread);
+
+ LuaThread *thread = new LuaThread(this);
+
+ mCurrentThread = thread;
+ mCurrentState = thread->mState;
+ return thread;
+}
+
+void LuaScript::prepareResume(Thread *thread)
+{
+ assert(nbArgs == -1);
+ assert(!mCurrentThread);
+
+ mCurrentThread = thread;
+ mCurrentState = static_cast<LuaThread*>(thread)->mState;
nbArgs = 0;
}
void LuaScript::push(int v)
{
assert(nbArgs >= 0);
- lua_pushinteger(mState, v);
+ lua_pushinteger(mCurrentState, v);
++nbArgs;
}
void LuaScript::push(const std::string &v)
{
assert(nbArgs >= 0);
- lua_pushstring(mState, v.c_str());
+ lua_pushstring(mCurrentState, v.c_str());
++nbArgs;
}
void LuaScript::push(Thing *v)
{
assert(nbArgs >= 0);
- lua_pushlightuserdata(mState, v);
+ lua_pushlightuserdata(mCurrentState, v);
++nbArgs;
}
@@ -77,8 +98,8 @@ void LuaScript::push(const std::list<InventoryItem> &itemList)
assert(nbArgs >= 0);
int position = 0;
- lua_createtable(mState, itemList.size(), 0);
- int itemTable = lua_gettop(mState);
+ lua_createtable(mCurrentState, itemList.size(), 0);
+ int itemTable = lua_gettop(mCurrentState);
for (std::list<InventoryItem>::const_iterator i = itemList.begin();
i != itemList.end();
@@ -88,8 +109,8 @@ void LuaScript::push(const std::list<InventoryItem> &itemList)
std::map<std::string, int> item;
item["id"] = i->itemId;
item["amount"] = i->amount;
- pushSTLContainer<std::string, int>(mState, item);
- lua_rawseti(mState, itemTable, ++position);
+ pushSTLContainer<std::string, int>(mCurrentState, item);
+ lua_rawseti(mCurrentState, itemTable, ++position);
}
++nbArgs;
}
@@ -97,56 +118,106 @@ void LuaScript::push(const std::list<InventoryItem> &itemList)
int LuaScript::execute()
{
assert(nbArgs >= 0);
- int res = lua_pcall(mState, nbArgs, 1, 1);
+ assert(!mCurrentThread);
+
+ int res = lua_pcall(mCurrentState, nbArgs, 1, 1);
nbArgs = -1;
- if (res || !(lua_isnil(mState, -1) || lua_isnumber(mState, -1)))
+
+ if (res || !(lua_isnil(mCurrentState, -1) || lua_isnumber(mCurrentState, -1)))
{
- const char *s = lua_tostring(mState, -1);
+ const char *s = lua_tostring(mCurrentState, -1);
LOG_WARN("Lua Script Error" << std::endl
<< " Script : " << mScriptFile << std::endl
<< " Error : " << (s ? s : "") << std::endl);
- lua_pop(mState, 1);
+ lua_pop(mCurrentState, 1);
return 0;
}
- res = lua_tointeger(mState, -1);
- lua_pop(mState, 1);
+ res = lua_tointeger(mCurrentState, -1);
+ lua_pop(mCurrentState, 1);
return res;
}
+bool LuaScript::resume()
+{
+ assert(nbArgs >= 0);
+ assert(mCurrentThread);
+
+ setMap(mCurrentThread->mMap);
+ int result = lua_resume(mCurrentState, nbArgs);
+ nbArgs = -1;
+ setMap(0);
+
+ if (result == 0) // Thread is done
+ {
+ if (lua_gettop(mCurrentState) > 0)
+ LOG_WARN("Ignoring values returned by script thread!");
+ }
+ else if (result == LUA_YIELD) // Thread has yielded
+ {
+ if (lua_gettop(mCurrentState) > 0)
+ LOG_WARN("Ignoring values passed to yield!");
+ }
+ else // Thread encountered an error
+ {
+ // Make a traceback using the debug.traceback function
+ lua_getglobal(mCurrentState, "debug");
+ lua_getfield(mCurrentState, -1, "traceback");
+ lua_pushvalue(mCurrentState, -3); // error string as first parameter
+ lua_pcall(mCurrentState, 1, 1, 0);
+
+ LOG_WARN("Lua Script Error:" << std::endl
+ << lua_tostring(mCurrentState, -1));
+ }
+
+ lua_settop(mCurrentState, 0);
+ const bool done = result != LUA_YIELD;
+
+ if (done)
+ {
+ // Clean up the current thread (not sure if this is the best place)
+ delete mCurrentThread;
+ }
+
+ mCurrentThread = 0;
+ mCurrentState = mRootState;
+
+ return done;
+}
+
void LuaScript::assignCallback(Script::Ref &function)
{
- assert(lua_isfunction(mState, -1));
+ assert(lua_isfunction(mRootState, -1));
// If there is already a callback set, replace it
if (function.isValid())
- luaL_unref(mState, LUA_REGISTRYINDEX, function.value);
+ luaL_unref(mRootState, LUA_REGISTRYINDEX, function.value);
- function.value = luaL_ref(mState, LUA_REGISTRYINDEX);
+ function.value = luaL_ref(mRootState, LUA_REGISTRYINDEX);
}
void LuaScript::load(const char *prog, const char *name)
{
- int res = luaL_loadbuffer(mState, prog, std::strlen(prog), name);
+ int res = luaL_loadbuffer(mRootState, prog, std::strlen(prog), name);
if (res)
{
switch (res) {
case LUA_ERRSYNTAX:
LOG_ERROR("Syntax error while loading Lua script: "
- << lua_tostring(mState, -1));
+ << lua_tostring(mRootState, -1));
break;
case LUA_ERRMEM:
LOG_ERROR("Memory allocation error while loading Lua script");
break;
}
- lua_pop(mState, 1);
+ lua_pop(mRootState, 1);
}
- else if (lua_pcall(mState, 0, 0, 1))
+ else if (lua_pcall(mRootState, 0, 0, 1))
{
LOG_ERROR("Failure while initializing Lua script: "
- << lua_tostring(mState, -1));
- lua_pop(mState, 1);
+ << lua_tostring(mRootState, -1));
+ lua_pop(mRootState, 1);
}
}
@@ -179,31 +250,47 @@ void LuaScript::processRemoveEvent(Thing *being)
/**
* Called when the server has recovered the value of a quest variable.
*/
-void LuaScript::getQuestCallback(Character *q, const std::string &name,
- const std::string &value, Script *script)
+void LuaScript::getQuestCallback(Character *q,
+ const std::string &value,
+ Script *script)
{
- if (mQuestReplyCallback.isValid())
- {
- script->prepare(mQuestReplyCallback);
- script->push(q);
- script->push(name);
- script->push(value);
- script->execute();
- }
+ Script::Thread *thread = q->getNpcThread();
+ if (!thread || thread->mState != Script::ThreadExpectingString)
+ return;
+
+ script->prepareResume(thread);
+ script->push(value);
+ script->resume();
}
/**
* Called when the server has recovered the post for a user.
*/
-void LuaScript::getPostCallback(Character *q, const std::string &sender,
- const std::string &letter, Script *script)
+void LuaScript::getPostCallback(Character *q,
+ const std::string &sender,
+ const std::string &letter,
+ Script *script)
{
- if (mPostReplyCallback.isValid())
- {
- script->prepare(mPostReplyCallback);
- script->push(q);
- script->push(sender);
- script->push(letter);
- script->execute();
- }
+ Script::Thread *thread = q->getNpcThread();
+ if (!thread || thread->mState != Script::ThreadExpectingTwoStrings)
+ return;
+
+ script->prepareResume(thread);
+ script->push(sender);
+ script->push(letter);
+ script->resume();
+}
+
+
+LuaScript::LuaThread::LuaThread(LuaScript *script) :
+ Thread(script)
+{
+ mState = lua_newthread(script->mRootState);
+ mRef = luaL_ref(script->mRootState, LUA_REGISTRYINDEX);
+}
+
+LuaScript::LuaThread::~LuaThread()
+{
+ LuaScript *luaScript = static_cast<LuaScript*>(mScript);
+ luaL_unref(luaScript->mRootState, LUA_REGISTRYINDEX, mRef);
}
diff --git a/src/scripting/luascript.h b/src/scripting/luascript.h
index e26fd9ef..57b8dc1a 100644
--- a/src/scripting/luascript.h
+++ b/src/scripting/luascript.h
@@ -44,8 +44,12 @@ class LuaScript : public Script
void load(const char *prog, const char *name);
+ Thread *newThread();
+
void prepare(Ref function);
+ void prepareResume(Thread *thread);
+
void push(int);
void push(const std::string &);
@@ -56,25 +60,24 @@ class LuaScript : public Script
int execute();
+ bool resume();
+
void assignCallback(Ref &function);
- static void getQuestCallback(Character *, const std::string &,
- const std::string &, Script *);
+ static void getQuestCallback(Character *,
+ const std::string &value,
+ Script *);
- static void getPostCallback(Character *, const std::string &,
- const std::string &, Script *);
+ static void getPostCallback(Character *,
+ const std::string &sender,
+ const std::string &letter,
+ Script *);
void processDeathEvent(Being *thing);
void processRemoveEvent(Thing *thing);
- static void setQuestReplyCallback(Script *script)
- { script->assignCallback(mQuestReplyCallback); }
-
- static void setPostReplyCallback(Script *script)
- { script->assignCallback(mPostReplyCallback); }
-
static void setDeathNotificationCallback(Script *script)
{ script->assignCallback(mDeathNotificationCallback); }
@@ -84,13 +87,24 @@ class LuaScript : public Script
static const char registryKey;
private:
- lua_State *mState;
+ class LuaThread : public Thread
+ {
+ public:
+ LuaThread(LuaScript *script);
+ ~LuaThread();
+
+ lua_State *mState;
+ int mRef;
+ };
+
+ lua_State *mRootState;
+ lua_State *mCurrentState;
int nbArgs;
- static Ref mQuestReplyCallback;
- static Ref mPostReplyCallback;
static Ref mDeathNotificationCallback;
static Ref mRemoveNotificationCallback;
+
+ friend class LuaThread;
};
static Script *LuaFactory()
diff --git a/src/scripting/luautil.cpp b/src/scripting/luautil.cpp
index 0a49e834..8bc72d3f 100644
--- a/src/scripting/luautil.cpp
+++ b/src/scripting/luautil.cpp
@@ -265,6 +265,18 @@ MapComposite *checkCurrentMap(lua_State *s, Script *script /* = 0 */)
return mapComposite;
}
+Script::Thread *checkCurrentThread(lua_State *s, Script *script /* = 0 */)
+{
+ if (!script)
+ script = getScript(s);
+
+ Script::Thread *thread = script->getCurrentThread();
+ if (!thread)
+ luaL_error(s, "function requires threaded execution");
+
+ return thread;
+}
+
void push(lua_State *s, int val)
{
diff --git a/src/scripting/luautil.h b/src/scripting/luautil.h
index 2a255280..89ea4800 100644
--- a/src/scripting/luautil.h
+++ b/src/scripting/luautil.h
@@ -21,10 +21,13 @@
#ifndef SCRIPTING_LUAUTIL_H
#define SCRIPTING_LUAUTIL_H
+#include "scripting/script.h"
+
extern "C" {
#include <lualib.h>
#include <lauxlib.h>
}
+
#include <string>
#include <list>
#include <map>
@@ -39,7 +42,6 @@ class MapObject;
class Monster;
class MonsterClass;
class NPC;
-class Script;
class StatusEffect;
class Thing;
@@ -168,6 +170,7 @@ MonsterClass * checkMonsterClass(lua_State *s, int p);
NPC * checkNPC(lua_State *s, int p);
MapComposite * checkCurrentMap(lua_State *s, Script *script = 0);
+Script::Thread* checkCurrentThread(lua_State *s, Script *script = 0);
/* Polymorphic wrapper for pushing variables.
diff --git a/src/scripting/script.cpp b/src/scripting/script.cpp
index 9ef069d5..6312d1ce 100644
--- a/src/scripting/script.cpp
+++ b/src/scripting/script.cpp
@@ -25,6 +25,7 @@
#include "game-server/being.h"
#include "utils/logger.h"
+#include <cassert>
#include <cstdlib>
#include <map>
@@ -38,10 +39,17 @@ Script::Ref Script::mCreateNpcDelayedCallback;
Script::Ref Script::mUpdateCallback;
Script::Script():
- mMap(NULL),
+ mCurrentThread(0),
+ mMap(0),
mEventListener(&scriptEventDispatch)
{}
+Script::~Script()
+{
+ // There should be no remaining threads when the Script gets deleted
+ assert(mThreads.empty());
+}
+
void Script::registerEngine(const std::string &name, Factory f)
{
if (!engines)
@@ -120,3 +128,35 @@ void Script::loadNPC(const std::string &name, int id, int x, int y,
push(y);
execute();
}
+
+
+/**
+ * Removes one element matching the given value by overwriting it with the last
+ * element and then popping the last element.
+ */
+template<typename T>
+static void fastRemoveOne(std::vector<T> &vector, T value)
+{
+ for (size_t i = vector.size() - 1; i >= 0; --i)
+ {
+ if (vector.at(i) == value)
+ {
+ vector.at(i) = vector.back();
+ vector.pop_back();
+ break;
+ }
+ }
+}
+
+Script::Thread::Thread(Script *script) :
+ mScript(script),
+ mState(ThreadPending),
+ mMap(0)
+{
+ script->mThreads.push_back(this);
+}
+
+Script::Thread::~Thread()
+{
+ fastRemoveOne(mScript->mThreads, this);
+}
diff --git a/src/scripting/script.h b/src/scripting/script.h
index 2fa3a0bf..b55f204e 100644
--- a/src/scripting/script.h
+++ b/src/scripting/script.h
@@ -26,6 +26,7 @@
#include <list>
#include <string>
+#include <vector>
class MapComposite;
class Thing;
@@ -56,16 +57,40 @@ class Script
* custom initialization and a definition of valid. It also makes the
* purpose clear.
*/
- class Ref {
- public:
- Ref() : value(-1) {}
- bool isValid() const { return value != -1; }
- int value;
+ class Ref
+ {
+ public:
+ Ref() : value(-1) {}
+ bool isValid() const { return value != -1; }
+ int value;
+ };
+
+ enum ThreadState {
+ ThreadPending,
+ ThreadPaused,
+ ThreadExpectingNumber,
+ ThreadExpectingString,
+ ThreadExpectingTwoStrings
+ };
+
+ /**
+ * A script thread. Meant to be extended by the Script subclass to
+ * store additional information.
+ */
+ class Thread
+ {
+ public:
+ Thread(Script *script);
+ virtual ~Thread();
+
+ Script * const mScript;
+ ThreadState mState;
+ MapComposite *mMap;
};
Script();
- virtual ~Script() {}
+ virtual ~Script();
/**
* Loads a chunk of text into script context and executes its global
@@ -96,12 +121,28 @@ class Script
virtual void update();
/**
+ * Creates a new script thread and makes it the current one. Script
+ * threads do not execute in parallel, but they can suspend execution
+ * and be resumed later.
+ *
+ * The new thread should be prepared as usual, but instead of
+ * execute(), the resume() function should be called.
+ */
+ virtual Thread *newThread() = 0;
+
+ /**
* Prepares a call to the referenced function.
* Only one function can be prepared at once.
*/
virtual void prepare(Ref function) = 0;
/**
+ * Prepares for resuming the given script thread.
+ * Only one thread can be resumed at once.
+ */
+ virtual void prepareResume(Thread *thread) = 0;
+
+ /**
* Pushes an integer argument for the function being prepared.
*/
virtual void push(int) = 0;
@@ -120,8 +161,7 @@ class Script
virtual void push(Thing *) = 0;
/**
- * Pushes a list of items with amounts to the
- * script engine.
+ * Pushes a list of items with amounts to the script engine.
*/
virtual void push(const std::list<InventoryItem> &itemList) = 0;
@@ -132,6 +172,14 @@ class Script
virtual int execute() = 0;
/**
+ * Starts or resumes the current thread. Deletes the thread when it is
+ * done.
+ *
+ * @return whether the thread is done executing.
+ */
+ virtual bool resume() = 0;
+
+ /**
* Assigns the current callback to the given \a function.
*
* Where the callback exactly comes from is up to the script engine.
@@ -139,6 +187,13 @@ class Script
virtual void assignCallback(Ref &function) = 0;
/**
+ * Returns the currently executing thread, or null when no thread is
+ * currently executing.
+ */
+ Thread *getCurrentThread() const
+ { return mCurrentThread; }
+
+ /**
* Sets associated map.
*/
void setMap(MapComposite *m)
@@ -165,15 +220,18 @@ class Script
protected:
std::string mScriptFile;
+ Thread *mCurrentThread;
private:
MapComposite *mMap;
EventListener mEventListener; /**< Tracking of being deaths. */
+ std::vector<Thread*> mThreads;
static Ref mCreateNpcDelayedCallback;
static Ref mUpdateCallback;
friend struct ScriptEventDispatch;
+ friend class Thread;
};
struct ScriptEventDispatch: EventDispatch
@@ -188,4 +246,4 @@ struct ScriptEventDispatch: EventDispatch
static ScriptEventDispatch scriptEventDispatch;
-#endif
+#endif // SCRIPTING_SCRIPT_H