Merge branch 'master' of git://github.com/peterix/dfhack

develop
jj 2012-04-17 22:42:29 +02:00
commit 3b54cf4657
15 changed files with 203 additions and 69 deletions

@ -209,7 +209,7 @@ Implemented features:
* ``ref:insert(index,item)``
Inserts a new item at the specified index. To add at the end,
use ``#ref`` as index.
use ``#ref``, or just ``'#'`` as index.
* ``ref:erase(index)``
@ -428,6 +428,11 @@ Currently it defines the following features:
If the thread owns the interactive console, shows a prompt
and returns the entered string. Otherwise returns *nil, error*.
Depending on the context, this function may actually yield the
running coroutine and let the C++ code release the core suspend
lock. Using an explicit ``dfhack.with_suspend`` will prevent
this, forcing the function to block on input with lock held.
* ``dfhack.interpreter([prompt[,env[,history_filename]]])``
Starts an interactive lua interpreter, using the specified prompt
@ -449,6 +454,10 @@ Currently it defines the following features:
Just like pcall, but also prints the error using printerr before
returning. Intended as a convenience function.
* ``dfhack.saferesume(coroutine[,args...])``
Compares to coroutine.resume like dfhack.safecall vs pcall.
* ``dfhack.with_suspend(f[,args...])``
Calls ``f`` with arguments after grabbing the DF core suspend lock.
@ -813,3 +822,37 @@ Core context specific functions:
* ``dfhack.is_core_context``
Boolean value; *true* in the core context.
* ``dfhack.onStateChange.foo = function(code)``
Event. Receives the same codes as plugin_onstatechange in C++.
Event type
----------
An event is just a lua table with a predefined metatable that
contains a __call metamethod. When it is invoked, it loops
through the table with next and calls all contained values.
This is intended as an extensible way to add listeners.
This type itself is available in any context, but only the
core context has the actual events defined by C++ code.
Features:
* ``dfhack.event.new()``
Creates a new instance of an event.
* ``event[key] = function``
Sets the function as one of the listeners.
**NOTE**: The ``df.NULL`` key is reserved for the use by
the C++ owner of the event, and has some special semantics.
* ``event(args...)``
Invokes all listeners contained in the event in an arbitrary
order using ``dfhack.safecall``.

@ -344,7 +344,10 @@ ul.auto-toc {
<li><a class="reference internal" href="#maps-module" id="id18">Maps module</a></li>
</ul>
</li>
<li><a class="reference internal" href="#core-interpreter-context" id="id19">Core interpreter context</a></li>
<li><a class="reference internal" href="#core-interpreter-context" id="id19">Core interpreter context</a><ul>
<li><a class="reference internal" href="#event-type" id="id20">Event type</a></li>
</ul>
</li>
</ul>
</li>
</ul>
@ -513,7 +516,7 @@ possible.</p>
</li>
<li><p class="first"><tt class="docutils literal">ref:insert(index,item)</tt></p>
<p>Inserts a new item at the specified index. To add at the end,
use <tt class="docutils literal">#ref</tt> as index.</p>
use <tt class="docutils literal">#ref</tt>, or just <tt class="docutils literal">'#'</tt> as index.</p>
</li>
<li><p class="first"><tt class="docutils literal">ref:erase(index)</tt></p>
<p>Removes the element at the given valid index.</p>
@ -705,6 +708,10 @@ works with DFHack output infrastructure.</p>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.lineedit([prompt[,history_filename]])</span></tt></p>
<p>If the thread owns the interactive console, shows a prompt
and returns the entered string. Otherwise returns <em>nil, error</em>.</p>
<p>Depending on the context, this function may actually yield the
running coroutine and let the C++ code release the core suspend
lock. Using an explicit <tt class="docutils literal">dfhack.with_suspend</tt> will prevent
this, forcing the function to block on input with lock held.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.interpreter([prompt[,env[,history_filename]]])</span></tt></p>
<p>Starts an interactive lua interpreter, using the specified prompt
@ -722,6 +729,9 @@ in C++, and dfhack.safecall.</p>
<p>Just like pcall, but also prints the error using printerr before
returning. Intended as a convenience function.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.saferesume(coroutine[,args...])</span></tt></p>
<p>Compares to coroutine.resume like dfhack.safecall vs pcall.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.with_suspend(f[,args...])</span></tt></p>
<p>Calls <tt class="docutils literal">f</tt> with arguments after grabbing the DF core suspend lock.
Suspending is necessary for accessing a consistent state of DF memory.</p>
@ -1021,9 +1031,36 @@ only context that can receive events from DF and plugins.</p>
<li><p class="first"><tt class="docutils literal">dfhack.is_core_context</tt></p>
<p>Boolean value; <em>true</em> in the core context.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.onStateChange.foo = function(code)</tt></p>
<p>Event. Receives the same codes as plugin_onstatechange in C++.</p>
</li>
</ul>
<div class="section" id="event-type">
<h3><a class="toc-backref" href="#id20">Event type</a></h3>
<p>An event is just a lua table with a predefined metatable that
contains a __call metamethod. When it is invoked, it loops
through the table with next and calls all contained values.
This is intended as an extensible way to add listeners.</p>
<p>This type itself is available in any context, but only the
core context has the actual events defined by C++ code.</p>
<p>Features:</p>
<ul>
<li><p class="first"><tt class="docutils literal">dfhack.event.new()</tt></p>
<p>Creates a new instance of an event.</p>
</li>
<li><p class="first"><tt class="docutils literal">event[key] = function</tt></p>
<p>Sets the function as one of the listeners.</p>
<p><strong>NOTE</strong>: The <tt class="docutils literal">df.NULL</tt> key is reserved for the use by
the C++ owner of the event, and has some special semantics.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">event(args...)</span></tt></p>
<p>Invokes all listeners contained in the event in an arbitrary
order using <tt class="docutils literal">dfhack.safecall</tt>.</p>
</li>
</ul>
</div>
</div>
</div>
</div>
</body>
</html>

@ -1098,6 +1098,18 @@ int DFHack::Lua::NewEvent(lua_State *state)
return 1;
}
static void do_invoke_event(lua_State *L, int argbase, int num_args, int errorfun)
{
for (int i = 0; i < num_args; i++)
lua_pushvalue(L, argbase+i);
if (lua_pcall(L, num_args, 0, errorfun) != LUA_OK)
{
report_error(L);
lua_pop(L, 1);
}
}
static void dfhack_event_invoke(lua_State *L, int base, bool from_c)
{
int event = base+1;
@ -1108,23 +1120,31 @@ static void dfhack_event_invoke(lua_State *L, int base, bool from_c)
lua_insert(L, errorfun);
int argbase = base+3;
lua_pushnil(L);
// stack: |base| event errorfun (args) key cb (args)
// stack: |base| event errorfun (args)
while (lua_next(L, event))
if (!from_c)
{
if (from_c && lua_islightuserdata(L, -1) && !lua_touserdata(L, -1))
continue;
// Invoke the NULL key first
lua_rawgetp(L, event, NULL);
for (int i = 0; i < num_args; i++)
lua_pushvalue(L, argbase+i);
if (lua_isnil(L, -1))
lua_pop(L, 1);
else
do_invoke_event(L, argbase, num_args, errorfun);
}
if (lua_pcall(L, num_args, 0, errorfun) != LUA_OK)
lua_pushnil(L);
// stack: |base| event errorfun (args) key || cb (args)
while (lua_next(L, event))
{
report_error(L);
// Skip the NULL key in the main loop
if (lua_islightuserdata(L, -2) && !lua_touserdata(L, -2))
lua_pop(L, 1);
}
else
do_invoke_event(L, argbase, num_args, errorfun);
}
lua_settop(L, base);
@ -1285,13 +1305,29 @@ lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state)
return state;
}
void DFHack::Lua::Core::onStateChange(color_ostream &out, int code) {
if (!State) return;
Lua::Push(State, code);
Lua::InvokeEvent(out, State, (void*)onStateChange, 1);
}
void DFHack::Lua::Core::Init(color_ostream &out)
{
if (State)
return;
State = luaL_newstate();
Lua::Open(out, State);
// Register events
lua_getglobal(State, "dfhack");
MakeEvent(State, (void*)onStateChange);
lua_setfield(State, -2, "onStateChange");
lua_pop(State, 1);
}
void DFHack::Lua::Core::Reset(color_ostream &out, const char *where)

@ -733,8 +733,18 @@ static int lookup_container_field(lua_State *state, int field, const char *mode
* Index verification: number and in range.
*/
static int check_container_index(lua_State *state, int len,
int fidx, int iidx, const char *mode)
int fidx, int iidx, const char *mode,
bool is_insert = false)
{
if (is_insert && len >= 0)
{
if (lua_type(state, iidx) == LUA_TSTRING
&& strcmp(lua_tostring(state, iidx), "#") == 0)
return len;
len++;
}
if (!lua_isnumber(state, iidx))
field_error(state, fidx, "invalid index", mode);
@ -867,8 +877,7 @@ static int method_container_insert(lua_State *state)
auto id = (container_identity*)lua_touserdata(state, UPVAL_CONTAINER_ID);
int len = id->lua_item_count(state, ptr, container_identity::COUNT_LEN);
if (len >= 0) len++;
int idx = check_container_index(state, len, UPVAL_METHOD_NAME, 2, "call");
int idx = check_container_index(state, len, UPVAL_METHOD_NAME, 2, "call", true);
if (!id->lua_insert(state, UPVAL_METHOD_NAME, ptr, idx, 3))
field_error(state, UPVAL_METHOD_NAME, "not supported", "call");

@ -641,6 +641,8 @@ void PluginManager::OnStateChange(color_ostream &out, state_change_event event)
{
all_plugins[i]->on_state_change(out, event);
}
Lua::Core::onStateChange(out, event);
}
// FIXME: doesn't check name collisions!

@ -278,6 +278,9 @@ namespace DFHack {namespace Lua {
void Init(color_ostream &out);
void Reset(color_ostream &out, const char *where);
// Events signalled by the core
void onStateChange(color_ostream &out, int code);
template<class T> inline void Push(T &arg) { Lua::Push(State, arg); }
template<class T> inline void Push(const T &arg) { Lua::Push(State, arg); }
template<class T> inline void PushVector(const T &arg) { Lua::PushVector(State, arg); }

@ -67,13 +67,13 @@ namespace DFHack
enum state_change_event
{
SC_WORLD_LOADED,
SC_WORLD_UNLOADED,
SC_MAP_LOADED,
SC_MAP_UNLOADED,
SC_VIEWSCREEN_CHANGED,
SC_CORE_INITIALIZED,
SC_BEGIN_UNLOAD
SC_WORLD_LOADED = 0,
SC_WORLD_UNLOADED = 1,
SC_MAP_LOADED = 2,
SC_MAP_UNLOADED = 3,
SC_VIEWSCREEN_CHANGED = 4,
SC_CORE_INITIALIZED = 5,
SC_BEGIN_UNLOAD = 6
};
struct DFHACK_EXPORT CommandReg {
const char *name;

@ -21,6 +21,17 @@ COLOR_LIGHTMAGENTA = 13
COLOR_YELLOW = 14
COLOR_WHITE = 15
-- Events
if dfhack.is_core_context then
SC_WORLD_LOADED = 0
SC_WORLD_UNLOADED = 1
SC_MAP_LOADED = 2
SC_MAP_UNLOADED = 3
SC_VIEWSCREEN_CHANGED = 4
SC_CORE_INITIALIZED = 5
end
-- Error handling
safecall = dfhack.safecall

@ -1 +1 @@
Subproject commit a545167050fee9eedd5e319b518d961f933154a3
Subproject commit e8036d3f13c6be0141899baae90f605ad11d5385

@ -196,6 +196,12 @@ private:
Core *c_;
};
std::ostream &operator<<(std::ostream &stream, const Brush& brush) {
inline std::ostream &operator<<(std::ostream &stream, const Brush& brush) {
stream << brush.str();
return stream;
}
inline std::ostream &operator<<(std::ostream &stream, const Brush* brush) {
stream << brush->str();
return stream;
}

@ -382,12 +382,15 @@ command_result df_bprobe (color_ostream &out, vector <string> & parameters)
out.print(", subtype %i", building.subtype);
break;
}
if(building.origin->isRoom())
if(building.origin->is_room) //isRoom())
out << ", is room";
else
out << ", not a room";
if(building.origin->getBuildStage()!=building.origin->getMaxBuildStage())
out << ", in construction";
out.print("\n");
}
return CR_OK;
}

@ -150,7 +150,7 @@ static command_result rename(color_ostream &out, vector <string> &parameters)
if (parameters.size() != 2)
return CR_WRONG_USAGE;
df::unit *unit = Gui::getSelectedUnit(out);
df::unit *unit = Gui::getSelectedUnit(out, true);
if (!unit)
return CR_WRONG_USAGE;
@ -161,7 +161,7 @@ static command_result rename(color_ostream &out, vector <string> &parameters)
if (parameters.size() != 2)
return CR_WRONG_USAGE;
df::unit *unit = Gui::getSelectedUnit(out);
df::unit *unit = Gui::getSelectedUnit(out, true);
if (!unit)
return CR_WRONG_USAGE;

@ -325,7 +325,7 @@ void printState(color_ostream &out)
{
out << "Filter: " << filter << std::endl
<< "Paint: " << paint << std::endl
<< "Brush: " << brush->str() << std::endl;
<< "Brush: " << brush << std::endl;
}
//zilpin: These two functions were giving me compile errors in VS2008, so I cheated with the C style loop below, just to get it to build.
@ -559,6 +559,7 @@ bool processTileType(color_ostream & out, TileType &paint, std::vector<std::stri
}
else if (option == "all" || option == "a")
{
loc--;
for (; loc < end; loc++)
{
std::string param = params[loc];

@ -140,7 +140,7 @@ static command_result tweak(color_ostream &out, vector <string> &parameters)
if (cmd == "clear-missing")
{
df::unit *unit = getSelectedUnit(out);
df::unit *unit = getSelectedUnit(out, true);
if (!unit)
return CR_FAILURE;
@ -157,7 +157,7 @@ static command_result tweak(color_ostream &out, vector <string> &parameters)
}
else if (cmd == "clear-ghostly")
{
df::unit *unit = getSelectedUnit(out);
df::unit *unit = getSelectedUnit(out, true);
if (!unit)
return CR_FAILURE;
@ -176,13 +176,9 @@ static command_result tweak(color_ostream &out, vector <string> &parameters)
}
else if (cmd == "fixmigrant")
{
df::unit *unit = getSelectedUnit(out);
df::unit *unit = getSelectedUnit(out, true);
if (!unit)
{
out << "No unit selected!" << endl;
return CR_FAILURE;
}
if(unit->race != df::global::ui->race_id)
{
@ -213,12 +209,10 @@ static command_result tweak(color_ostream &out, vector <string> &parameters)
{
// force a unit into your fort, regardless of civ or race
// allows to "steal" caravan guards etc
df::unit *unit = getSelectedUnit(out);
df::unit *unit = getSelectedUnit(out, true);
if (!unit)
{
out << "No unit selected!" << endl;
return CR_FAILURE;
}
if (unit->flags2.bits.resident)
unit->flags2.bits.resident = 0;
if(unit->flags1.bits.merchant)

@ -897,6 +897,10 @@ int32_t findCageAtCursor()
building->z == cursor->z))
continue;
// don't set id if cage is not constructed yet
if(building->getBuildStage()!=building->getMaxBuildStage())
break;
if(isCage(building))
{
foundID = building->id;
@ -1065,10 +1069,10 @@ bool isInBuiltCageRoom(df::unit* unit)
{
df::building* building = world->buildings.all[b];
// !!! for whatever reason isRoom() returns true if a cage is not a room
// !!! and false if it was defined as a room/zoo ingame
// !!! (seems not general behaviour, activity zones return false, for example)
if(building->isRoom())
// !!! building->isRoom() returns true if the building can be made a room but currently isn't
// !!! except for coffins/tombs which always return false
// !!! using the bool is_room however gives the correct state/value
if(!building->is_room)
continue;
if(building->getType() == building_type::Cage)
@ -1122,6 +1126,10 @@ df::building * getBuiltCageAtPos(df::coord pos)
&& building->y1 == pos.y
&& building->z == pos.z )
{
// don't set pointer if not constructed yet
if(building->getBuildStage()!=building->getMaxBuildStage())
break;
cage = building;
break;
}
@ -1594,11 +1602,6 @@ void zoneInfo(color_ostream & out, df::building* building, bool verbose)
else
out << "not active";
//if(building->isRoom())
// out <<", room";
//else
// out << ", not a room";
if(civ->zone_flags.bits.pen_pasture)
out << ", pen/pasture";
else if (civ->zone_flags.bits.pit_pond)
@ -1655,16 +1658,7 @@ void cageInfo(color_ostream & out, df::building* building, bool verbose)
<< " z:" << building->z
<< endl;
//if(building->isRoom())
// out <<", bldg room";
//else
// out << ", bldg not a room";
df::building_cagest * cage = (df::building_cagest*) building;
//if(cage->isRoom())
// out <<", cage is room";
//else
// out << ", cage is not a room";
int32_t creaturecount = cage->assigned_creature.size();
out << "Creatures in this cage: " << creaturecount << endl;
@ -2465,12 +2459,9 @@ command_result df_zone (color_ostream &out, vector <string> & parameters)
else
{
// must have unit selected
df::unit *unit = getSelectedUnit(out);
df::unit *unit = getSelectedUnit(out, true);
if (!unit)
{
out << "No unit selected." << endl;
return CR_WRONG_USAGE;
}
if(unit_info)
{
@ -2503,18 +2494,16 @@ command_result df_zone (color_ostream &out, vector <string> & parameters)
if(building_unassign)
{
// must have unit selected
df::unit *unit = getSelectedUnit(out);
df::unit *unit = getSelectedUnit(out, true);
if (!unit)
{
out << "No unit selected." << endl;
return CR_WRONG_USAGE;
}
// remove assignment reference from unit and old zone
if(unassignUnitFromBuilding(unit))
out << "Unit unassigned." << endl;
else
out << "Unit is not assigned to an activity zone!" << endl;
return CR_OK;
}