From 567397df857d216eba4778bcef96309704b5d376 Mon Sep 17 00:00:00 2001 From: Eric Wald Date: Sun, 7 Sep 2014 13:52:07 -0600 Subject: [PATCH 1/7] Stockpile iterator. Efficient way to operate on all items stored on a stockpile. Doesn't currently check whether the items match the stockpile settings, but does ignore empty containers assigned to the stockpile. --- library/include/modules/Buildings.h | 98 +++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/library/include/modules/Buildings.h b/library/include/modules/Buildings.h index 9bb5c7e54..a4bf05249 100644 --- a/library/include/modules/Buildings.h +++ b/library/include/modules/Buildings.h @@ -27,14 +27,18 @@ distribution. #include "DataDefs.h" #include "Types.h" #include "df/building.h" +#include "df/building_stockpilest.h" #include "df/building_type.h" #include "df/civzone_type.h" #include "df/furnace_type.h" +#include "df/item.h" #include "df/workshop_type.h" #include "df/construction_type.h" #include "df/shop_type.h" #include "df/siegeengine_type.h" #include "df/trap_type.h" +#include "modules/Items.h" +#include "modules/Maps.h" namespace df { @@ -186,5 +190,99 @@ DFHACK_EXPORT bool deconstruct(df::building *bld); void updateBuildings(color_ostream& out, void* ptr); void clearBuildings(color_ostream& out); +/** + * Iterates over the items stored on a stockpile. + * (For stockpiles with containers, yields the containers, not their contents.) + * + * Usage: + * + * Buildings::StockpileIterator stored; + * for (stored.begin(stockpile); !stored.done(); ++stored) { + * df::item *item = *stored; + * } + * + * Implementation detail: Uses tile blocks for speed. + * For each tile block that contains at least part of the stockpile, + * starting at the top left and moving right, row by row, + * the block's items are checked for anything on the ground within that stockpile. + */ +class DFHACK_EXPORT StockpileIterator : public std::iterator +{ + df::building_stockpilest* stockpile; + df::map_block* block; + size_t current; + df::item *item; + +public: + StockpileIterator() { + stockpile = NULL; + block = NULL; + item = NULL; + } + + StockpileIterator& operator++() { + while (stockpile) { + if (block) { + // Check the next item in the current block. + ++current; + } else { + // Start with the top-left block covering the stockpile. + block = Maps::getTileBlock(stockpile->x1, stockpile->y1, stockpile->z); + current = 0; + } + + while (current >= block->items.size()) { + // Out of items in this block; find the next block to search. + if (block->map_pos.x + 16 < stockpile->x2) { + block = Maps::getTileBlock(block->map_pos.x + 16, block->map_pos.y, stockpile->z); + current = 0; + } else if (block->map_pos.y + 16 < stockpile->y2) { + block = Maps::getTileBlock(stockpile->x1, block->map_pos.y + 16, stockpile->z); + current = 0; + } else { + // All items in all blocks have been checked. + block = NULL; + item = NULL; + return *this; + } + } + + // If the current item isn't properly stored, move on to the next. + item = df::item::find(block->items[current]); + if (!item->flags.bits.on_ground) { + continue; + } + + if (!Buildings::containsTile(stockpile, item->pos, false)) { + continue; + } + + // Ignore empty bins, barrels, and wheelbarrows assigned here. + if (item->isAssignedToThisStockpile(stockpile->id)) { + auto ref = Items::getGeneralRef(item, df::general_ref_type::CONTAINS_ITEM); + if (!ref) continue; + } + + // Found a valid item; yield it. + break; + } + + return *this; + } + + void begin(df::building_stockpilest* sp) { + stockpile = sp; + operator++(); + } + + df::item* operator*() { + return item; + } + + bool done() { + return block == NULL; + } +}; + } } From 3c58bd3948ed053c2707e5a4bc7a0295b099a51d Mon Sep 17 00:00:00 2001 From: Eric Wald Date: Sun, 7 Sep 2014 14:03:51 -0600 Subject: [PATCH 2/7] Switching autotrade to use StockpileIterator. This should speed it up a bit, and prevents it from marking a stream of empty bins. --- plugins/autotrade.cpp | 20 ++++++++++---------- plugins/uicommon.h | 5 +++++ 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/plugins/autotrade.cpp b/plugins/autotrade.cpp index ccc42f0e8..4cfd2913e 100644 --- a/plugins/autotrade.cpp +++ b/plugins/autotrade.cpp @@ -8,6 +8,7 @@ #include "df/viewscreen_dwarfmodest.h" #include "df/viewscreen_tradegoodsst.h" #include "df/building_stockpilest.h" +#include "modules/Buildings.h" #include "modules/Items.h" #include "df/building_tradedepotst.h" #include "df/general_ref_building_holderst.h" @@ -202,8 +203,6 @@ static void mark_all_in_stockpiles(vector &stockpiles) if (!depot_info.findDepot()) return; - std::vector &items = world->items.other[items_other_id::IN_PLAY]; - // Precompute a bitmask with the bad flags df::item_flags bad_flags; @@ -218,18 +217,19 @@ static void mark_all_in_stockpiles(vector &stockpiles) size_t marked_count = 0; size_t error_count = 0; - for (size_t i = 0; i < items.size(); i++) + for (auto it = stockpiles.begin(); it != stockpiles.end(); it++) { - df::item *item = items[i]; - if (item->flags.whole & bad_flags.whole) - continue; - - if (!is_valid_item(item)) + if (!it->isValid()) continue; - for (auto it = stockpiles.begin(); it != stockpiles.end(); it++) + Buildings::StockpileIterator stored; + for (stored.begin(it->getStockpile()); !stored.done(); ++stored) { - if (!it->inStockpile(item)) + df::item *item = *stored; + if (item->flags.whole & bad_flags.whole) + continue; + + if (!is_valid_item(item)) continue; // In case of container, check contained items for mandates diff --git a/plugins/uicommon.h b/plugins/uicommon.h index 1daabf4c7..c900a585e 100644 --- a/plugins/uicommon.h +++ b/plugins/uicommon.h @@ -346,6 +346,11 @@ public: return this->sp == sp; } + df::building_stockpilest* getStockpile() + { + return sp; + } + protected: int32_t id; df::building_stockpilest* sp; From 8631da7e4e93af1900efbec2a23767c6daa3dff9 Mon Sep 17 00:00:00 2001 From: Eric Wald Date: Sun, 7 Sep 2014 14:10:27 -0600 Subject: [PATCH 3/7] Convenience method to collect a vector from StockpileIterator. --- library/include/modules/Buildings.h | 5 +++++ library/modules/Buildings.cpp | 13 +++++++++++++ 2 files changed, 18 insertions(+) diff --git a/library/include/modules/Buildings.h b/library/include/modules/Buildings.h index a4bf05249..db12bb73e 100644 --- a/library/include/modules/Buildings.h +++ b/library/include/modules/Buildings.h @@ -284,5 +284,10 @@ public: } }; +/** + * Collects items stored on a stockpile into a vector. + */ +DFHACK_EXPORT void getStockpileContents(df::building_stockpilest *stockpile, std::vector *items); + } } diff --git a/library/modules/Buildings.cpp b/library/modules/Buildings.cpp index 3d836db52..885edfa02 100644 --- a/library/modules/Buildings.cpp +++ b/library/modules/Buildings.cpp @@ -1163,3 +1163,16 @@ void Buildings::updateBuildings(color_ostream& out, void* ptr) corner2.erase(id); } } + +void Buildings::getStockpileContents(df::building_stockpilest *stockpile, std::vector *items) +{ + CHECK_NULL_POINTER(stockpile); + + items->clear(); + + Buildings::StockpileIterator stored; + for (stored.begin(stockpile); !stored.done(); ++stored) { + df::item *item = *stored; + items->push_back(item); + } +} From 74e709ec197ab8c65a754e7e7146ad0eab284ead Mon Sep 17 00:00:00 2001 From: Eric Wald Date: Sun, 7 Sep 2014 14:52:08 -0600 Subject: [PATCH 4/7] Adding getStockpileContents to the Lua API. --- Lua API.html | 4 ++++ Lua API.rst | 5 +++++ library/LuaApi.cpp | 9 +++++++++ 3 files changed, 18 insertions(+) diff --git a/Lua API.html b/Lua API.html index 1a2888eb0..7d99228bf 100644 --- a/Lua API.html +++ b/Lua API.html @@ -1586,6 +1586,10 @@ are removed from extents. If allow_occupied, t

Checks if a bridge constructed at specified position would have support from terrain, and thus won't collapse if retracted.

+
  • dfhack.buildings.getStockpileContents(stockpile)

    +

    Returns a list of items stored on the given stockpile. +Ignores empty bins, barrels, and wheelbarrows assigned as storage and transport for that stockpile.

    +
  • Low-level building creation functions;

      diff --git a/Lua API.rst b/Lua API.rst index 7283f196f..d770271ae 100644 --- a/Lua API.rst +++ b/Lua API.rst @@ -1394,6 +1394,11 @@ Buildings module Checks if a bridge constructed at specified position would have support from terrain, and thus won't collapse if retracted. +* ``dfhack.buildings.getStockpileContents(stockpile)`` + + Returns a list of items stored on the given stockpile. + Ignores empty bins, barrels, and wheelbarrows assigned as storage and transport for that stockpile. + Low-level building creation functions; * ``dfhack.buildings.allocInstance(pos, type, subtype, custom)`` diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 3fd18a8d6..f2693363a 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -1744,11 +1744,20 @@ int buildings_setSize(lua_State *state) } +static int buildings_getStockpileContents(lua_State *state) +{ + std::vector pvec; + Buildings::getStockpileContents(Lua::CheckDFObject(state,1),&pvec); + Lua::PushVector(state, pvec); + return 1; +} + static const luaL_Reg dfhack_buildings_funcs[] = { { "findAtTile", buildings_findAtTile }, { "findCivzonesAt", buildings_findCivzonesAt }, { "getCorrectSize", buildings_getCorrectSize }, { "setSize", &Lua::CallWithCatchWrapper }, + { "getStockpileContents", buildings_getStockpileContents}, { NULL, NULL } }; From 1ce07c063db4b4ea1ed22cec41c84b510330077b Mon Sep 17 00:00:00 2001 From: Eric Wald Date: Sun, 7 Sep 2014 14:55:34 -0600 Subject: [PATCH 5/7] Canonical way to find the stockpile by id. --- plugins/lua/stockflow.lua | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/plugins/lua/stockflow.lua b/plugins/lua/stockflow.lua index 2f564cec1..86018e1ae 100644 --- a/plugins/lua/stockflow.lua +++ b/plugins/lua/stockflow.lua @@ -1,7 +1,6 @@ local _ENV = mkmodule('plugins.stockflow') local gui = require "gui" -local utils = require "utils" reaction_list = reaction_list or {} saved_orders = saved_orders or {} @@ -116,10 +115,9 @@ function collect_orders() local result = {} local entries = dfhack.persistent.get_all("stockflow/entry", true) if entries then - local stockpiles = df.global.world.buildings.other.STOCKPILE for _, entry in ipairs(entries) do local spid = entry.ints[entry_ints.stockpile_id] - local stockpile = utils.binsearch(stockpiles, spid, "id") + local stockpile = df.building.find(spid) if stockpile then local order_number = entry.ints[entry_ints.order_number] if reaction_list[order_number] and entry.value == reaction_list[order_number].name then From 83b1317173bc9fef77ff98da137cfb8b723572cf Mon Sep 17 00:00:00 2001 From: Eric Wald Date: Sun, 7 Sep 2014 15:07:57 -0600 Subject: [PATCH 6/7] Switching stockflow to the internal getStockpileContents. This should speed it up over checking each space individually, but no longer counts spaces with empty assigned bins and barrels as empty. --- plugins/lua/stockflow.lua | 64 +++++++-------------------------------- 1 file changed, 11 insertions(+), 53 deletions(-) diff --git a/plugins/lua/stockflow.lua b/plugins/lua/stockflow.lua index 86018e1ae..d1c06525a 100644 --- a/plugins/lua/stockflow.lua +++ b/plugins/lua/stockflow.lua @@ -973,49 +973,28 @@ function create_orders(order, amount) df.global.world.manager_orders:insert('#', new_order) end -function findItemsAtTile(x, y, z) - -- There should be a faster and easier way to do this... - local found = {} - local items = dfhack.maps.getTileBlock(x, y, z).items - for _, item_id in ipairs(items) do - local item = df.item.find(item_id) - if item.pos.x == x and item.pos.y == y and item.flags.on_ground then - found[#found+1] = item - end - end - - return found -end - function countContents(container, settings) local total = 0 - local blocking = false for _, item in ipairs(dfhack.items.getContainedItems(container)) do if item.flags.container then -- Recursively count the total of items contained. -- Not likely to go more than two levels deep. - local subtotal, subblocked = countContents(item, settings) + local subtotal = countContents(item, settings) if subtotal > 0 then -- Ignore the inner container itself; -- generally, only the contained items matter. total = total + subtotal - elseif subblocked then - blocking = true elseif matches_stockpile(item, settings) then -- The container may or may not be empty, -- but is stockpiled as a container itself. total = total + 1 - else - blocking = true end elseif matches_stockpile(item, settings) then total = total + 1 - else - blocking = true end end - return total, blocking + return total end function check_stockpiles(verbose) @@ -1046,19 +1025,21 @@ function check_pile(sp, verbose) if not designation.liquid_type then if not occupancy.item then empty = empty + 1 - else - local item_count, blocked = count_pile_items(sp, x, y) - if item_count > 0 then - filled = filled + item_count - elseif not blocked then - empty = empty + 1 - end end end end end end + for _, item in ipairs(dfhack.buildings.getStockpileContents(sp)) do + if item:isAssignedToThisStockpile(sp.id) then + -- This is a bin or barrel associated with the stockpile. + filled = filled + countContents(item, sp.settings) + elseif matches_stockpile(item, sp.settings) then + filled = filled + 1 + end + end + if verbose then print("Stockpile #"..sp.stockpile_number, string.format("%3d spaces", numspaces), @@ -1069,29 +1050,6 @@ function check_pile(sp, verbose) return filled, empty end -function count_pile_items(sp, x, y) - local item_count = 0 - local blocked = false - for _, item in ipairs(findItemsAtTile(x, y, sp.z)) do - if item:isAssignedToThisStockpile(sp.id) then - -- This is a bin or barrel associated with the stockpile. - -- If it's empty, it doesn't count as blocking the stockpile space. - -- Oddly, when empty, item.flags.container might be false. - local subtotal, subblocked = countContents(item, sp.settings) - item_count = item_count + subtotal - if subblocked then - blocked = true - end - elseif matches_stockpile(item, sp.settings) then - item_count = item_count + 1 - else - blocked = true - end - end - - return item_count, blocked -end - function matches_stockpile(item, settings) -- Check whether the item matches the stockpile. -- FIXME: This is starting to look like a whole lot of work. From 0f311a76063e344ba890977f521f5d5d766147db Mon Sep 17 00:00:00 2001 From: Eric Wald Date: Sun, 7 Sep 2014 15:11:11 -0600 Subject: [PATCH 7/7] Removing dead duplicate code. --- plugins/autolabor.cpp | 203 ------------------------------------------ 1 file changed, 203 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index ed907a788..05855a749 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -1480,206 +1480,3 @@ command_result autolabor (color_ostream &out, std::vector & parame return CR_OK; } } - -struct StockpileInfo { - df::building_stockpilest* sp; - int size; - int free; - int x1, x2, y1, y2, z; - -public: - StockpileInfo(df::building_stockpilest *sp_) : sp(sp_) - { - MapExtras::MapCache mc; - - z = sp_->z; - x1 = sp_->room.x; - x2 = sp_->room.x + sp_->room.width; - y1 = sp_->room.y; - y2 = sp_->room.y + sp_->room.height; - int e = 0; - size = 0; - free = 0; - for (int y = y1; y < y2; y++) - for (int x = x1; x < x2; x++) - if (sp_->room.extents[e++] == 1) - { - size++; - DFCoord cursor (x,y,z); - uint32_t blockX = x / 16; - uint32_t tileX = x % 16; - uint32_t blockY = y / 16; - uint32_t tileY = y % 16; - MapExtras::Block * b = mc.BlockAt(cursor/16); - if(b && b->is_valid()) - { - auto &block = *b->getRaw(); - df::tile_occupancy &occ = block.occupancy[tileX][tileY]; - if (!occ.bits.item) - free++; - } - } - } - - bool isFull() { return free == 0; } - - bool canHold(df::item *i) - { - return false; - } - - bool inStockpile(df::item *i) - { - df::item *container = Items::getContainer(i); - if (container) - return inStockpile(container); - - if (i->pos.z != z) return false; - if (i->pos.x < x1 || i->pos.x >= x2 || - i->pos.y < y1 || i->pos.y >= y2) return false; - int e = (i->pos.x - x1) + (i->pos.y - y1) * sp->room.width; - return sp->room.extents[e] == 1; - } - - int getId() { return sp->id; } -}; - -static int stockcheck(color_ostream &out, vector & parameters) -{ - int count = 0; - - std::vector stockpiles; - - for (int i = 0; i < world->buildings.all.size(); ++i) - { - df::building *build = world->buildings.all[i]; - auto type = build->getType(); - if (building_type::Stockpile == type) - { - df::building_stockpilest *sp = virtual_cast(build); - StockpileInfo *spi = new StockpileInfo(sp); - stockpiles.push_back(spi); - } - - } - - std::vector &items = world->items.other[items_other_id::IN_PLAY]; - - // Precompute a bitmask with the bad flags - df::item_flags bad_flags; - bad_flags.whole = 0; - -#define F(x) bad_flags.bits.x = true; - F(dump); F(forbid); F(garbage_collect); - F(hostile); F(on_fire); F(rotten); F(trader); - F(in_building); F(construction); F(artifact); - F(spider_web); F(owned); F(in_job); -#undef F - - for (size_t i = 0; i < items.size(); i++) - { - df::item *item = items[i]; - if (item->flags.whole & bad_flags.whole) - continue; - - // we really only care about MEAT, FISH, FISH_RAW, PLANT, CHEESE, FOOD, and EGG - - df::item_type typ = item->getType(); - if (typ != item_type::MEAT && - typ != item_type::FISH && - typ != item_type::FISH_RAW && - typ != item_type::PLANT && - typ != item_type::CHEESE && - typ != item_type::FOOD && - typ != item_type::EGG) - continue; - - df::item *container = 0; - df::unit *holder = 0; - df::building *building = 0; - - for (size_t i = 0; i < item->general_refs.size(); i++) - { - df::general_ref *ref = item->general_refs[i]; - - switch (ref->getType()) - { - case general_ref_type::CONTAINED_IN_ITEM: - container = ref->getItem(); - break; - - case general_ref_type::UNIT_HOLDER: - holder = ref->getUnit(); - break; - - case general_ref_type::BUILDING_HOLDER: - building = ref->getBuilding(); - break; - - default: - break; - } - } - - df::item *nextcontainer = container; - df::item *lastcontainer = 0; - - while(nextcontainer) { - df::item *thiscontainer = nextcontainer; - nextcontainer = 0; - for (size_t i = 0; i < thiscontainer->general_refs.size(); i++) - { - df::general_ref *ref = thiscontainer->general_refs[i]; - - switch (ref->getType()) - { - case general_ref_type::CONTAINED_IN_ITEM: - lastcontainer = nextcontainer = ref->getItem(); - break; - - case general_ref_type::UNIT_HOLDER: - holder = ref->getUnit(); - break; - - case general_ref_type::BUILDING_HOLDER: - building = ref->getBuilding(); - break; - - default: - break; - } - } - } - - if (holder) - continue; // carried items do not rot as far as i know - - if (building) { - df::building_type btype = building->getType(); - if (btype == building_type::TradeDepot || - btype == building_type::Wagon) - continue; // items in trade depot or the embark wagon do not rot - - if (typ == item_type::EGG && btype ==building_type::NestBox) - continue; // eggs in nest box do not rot - } - - int canHoldCount = 0; - StockpileInfo *current = 0; - - for (int idx = 0; idx < stockpiles.size(); idx++) - { - StockpileInfo *spi = stockpiles[idx]; - if (spi->canHold(item)) canHoldCount++; - if (spi->inStockpile(item)) current=spi; - } - - if (current) - continue; - - count++; - - } - - return count; -}