use const char *, not std::string for efficiency

so we can actually process large maps without OOMing
develop
myk002 2021-09-10 14:19:50 -07:00 committed by Myk
parent 772e386427
commit 42e04fc6ec
1 changed files with 177 additions and 159 deletions

@ -113,55 +113,72 @@ struct tile_context {
df::building* b = NULL;
};
static pair<uint32_t, uint32_t> get_building_size(df::building *b) {
return pair<uint32_t, uint32_t>(b->x2 - b->x1 + 1, b->y2 - b->y1 + 1);
}
static const char * if_pretty(const tile_context &ctx, const char *c) {
return ctx.pretty ? c : "";
}
static void get_tile_dig(const df::coord &pos, const tile_context &,
string &str) {
// the number of different strings we use is very small so we use a string cache
// to limit the number of memory allocations we make. this significantly speeds
// up processing and allows us to handle very large maps (e.g. 16x16 embarks)
// without running out of memory.
// if NULL is passed as the str, the cache is cleared
static const char * cache(const char *str) {
// this local static assumes that no two blueprints are being generated at
// the same time, which is currently ensured by the higher-level DFHack
// command handling code. if this assumption ever becomes untrue, we'll
// need to protect the cache with thread synchronization primitives.
static std::set<string> _cache;
if (!str) {
_cache.clear();
return NULL;
}
return _cache.emplace(str).first->c_str();
}
static const char * get_tile_dig(const df::coord &pos, const tile_context &) {
df::tiletype *tt = Maps::getTileType(pos);
switch (tileShape(tt ? *tt : tiletype::Void))
{
case tiletype_shape::EMPTY:
case tiletype_shape::RAMP_TOP:
str = "h"; break;
return "h";
case tiletype_shape::FLOOR:
case tiletype_shape::BOULDER:
case tiletype_shape::PEBBLES:
case tiletype_shape::BROOK_TOP:
str = "d"; break;
return "d";
case tiletype_shape::FORTIFICATION:
str = "F"; break;
return "F";
case tiletype_shape::STAIR_UP:
str = "u"; break;
return "u";
case tiletype_shape::STAIR_DOWN:
str = "j"; break;
return "j";
case tiletype_shape::STAIR_UPDOWN:
str = "i"; break;
return "i";
case tiletype_shape::RAMP:
str = "r"; break;
return "r";
case tiletype_shape::WALL:
default:
break;
return NULL;
}
}
static void do_block_building(const tile_context &ctx, string &str, string s,
bool at_target_pos, bool *add_size = NULL) {
static pair<uint32_t, uint32_t> get_building_size(df::building *b) {
return pair<uint32_t, uint32_t>(b->x2 - b->x1 + 1, b->y2 - b->y1 + 1);
}
static const char * if_pretty(const tile_context &ctx, const char *c) {
return ctx.pretty ? c : "";
}
static const char * do_block_building(const tile_context &ctx, const char *s,
bool at_target_pos,
bool *add_size = NULL) {
if(!at_target_pos) {
str = if_pretty(ctx, "`");
return;
return if_pretty(ctx, "`");
}
str = s;
if (add_size)
*add_size = true;
return s;
}
static string get_bridge_str(df::building *b) {
static const char * get_bridge_str(df::building *b) {
df::building_bridgest *bridge = virtual_cast<df::building_bridgest>(b);
if (!bridge)
return "g";
@ -177,13 +194,13 @@ static string get_bridge_str(df::building *b) {
}
}
static string get_siege_str(df::building *b) {
static const char * get_siege_str(df::building *b) {
df::building_siegeenginest *se =
virtual_cast<df::building_siegeenginest>(b);
return !se || se->type == df::siegeengine_type::Catapult ? "ic" : "ib";
}
static string get_workshop_str(df::building *b) {
static const char * get_workshop_str(df::building *b) {
df::building_workshopst *ws = virtual_cast<df::building_workshopst>(b);
if (!ws)
return "~";
@ -219,7 +236,7 @@ static string get_workshop_str(df::building *b) {
}
}
static string get_furnace_str(df::building *b) {
static const char * get_furnace_str(df::building *b) {
df::building_furnacest *furnace = virtual_cast<df::building_furnacest>(b);
if (!furnace)
return "~";
@ -238,7 +255,7 @@ static string get_furnace_str(df::building *b) {
}
}
static string get_construction_str(df::building *b) {
static const char * get_construction_str(df::building *b) {
df::building_constructionst *cons =
virtual_cast<df::building_constructionst>(b);
if (!cons)
@ -288,7 +305,7 @@ static string get_construction_str(df::building *b) {
}
}
static string get_trap_str(df::building *b) {
static const char * get_trap_str(df::building *b) {
df::building_trapst *trap = virtual_cast<df::building_trapst>(b);
if (!trap)
return "~";
@ -322,14 +339,14 @@ static string get_trap_str(df::building *b) {
case 500: buf << "a";
case 10000: buf << "a";
}
return buf.str();
return cache(buf.str().c_str());
}
default:
return "~";
}
}
static string get_screw_pump_str(df::building *b) {
static const char * get_screw_pump_str(df::building *b) {
df::building_screw_pumpst *sp = virtual_cast<df::building_screw_pumpst>(b);
if (!sp)
return "~";
@ -345,7 +362,7 @@ static string get_screw_pump_str(df::building *b) {
}
}
static string get_water_wheel_str(df::building *b) {
static const char * get_water_wheel_str(df::building *b) {
df::building_water_wheelst *ww =
virtual_cast<df::building_water_wheelst>(b);
if (!ww)
@ -354,7 +371,7 @@ static string get_water_wheel_str(df::building *b) {
return ww->is_vertical ? "Mw" : "Mws";
}
static string get_axle_str(df::building *b) {
static const char * get_axle_str(df::building *b) {
df::building_axle_horizontalst *ah =
virtual_cast<df::building_axle_horizontalst>(b);
if (!ah)
@ -363,7 +380,7 @@ static string get_axle_str(df::building *b) {
return ah->is_vertical ? "Mhs" : "Mh";
}
static string get_roller_str(df::building *b) {
static const char * get_roller_str(df::building *b) {
df::building_rollersst *r = virtual_cast<df::building_rollersst>(b);
if (!r)
return "~";
@ -378,192 +395,192 @@ static string get_roller_str(df::building *b) {
}
}
static string get_expansion_str(df::building *b) {
pair<uint32_t, uint32_t> size = get_building_size(b);
std::ostringstream s;
s << "(" << size.first << "x" << size.second << ")";
return s.str();
}
static void get_tile_build(const df::coord &pos, const tile_context &ctx,
string &str) {
if (!ctx.b || ctx.b->getType() == building_type::Stockpile) {
return;
}
static const char * get_build_keys(const df::coord &pos,
const tile_context &ctx,
bool &add_size) {
bool at_nw_corner = static_cast<int32_t>(pos.x) == ctx.b->x1
&& static_cast<int32_t>(pos.y) == ctx.b->y1;
bool at_se_corner = static_cast<int32_t>(pos.x) == ctx.b->x2
&& static_cast<int32_t>(pos.y) == ctx.b->y2;
bool at_center = static_cast<int32_t>(pos.x) == ctx.b->centerx
&& static_cast<int32_t>(pos.y) == ctx.b->centery;
bool add_size = false;
switch(ctx.b->getType()) {
case building_type::Armorstand:
str = "a"; break;
return "a";
case building_type::Bed:
str = "b"; break;
return "b";
case building_type::Chair:
str = "c"; break;
return "c";
case building_type::Door:
str = "d"; break;
return "d";
case building_type::Floodgate:
str = "x"; break;
return "x";
case building_type::Cabinet:
str = "f"; break;
return "f";
case building_type::Box:
str = "h"; break;
return "h";
//case building_type::Kennel is missing
case building_type::FarmPlot:
do_block_building(ctx, str, "p", at_nw_corner, &add_size); break;
return do_block_building(ctx, "p", at_nw_corner, &add_size);
case building_type::Weaponrack:
str = "r"; break;
return "r";
case building_type::Statue:
str = "s"; break;
return "s";
case building_type::Table:
str = "t"; break;
return "t";
case building_type::RoadPaved:
do_block_building(ctx, str, "o", at_nw_corner, &add_size); break;
return do_block_building(ctx, "o", at_nw_corner, &add_size);
case building_type::RoadDirt:
do_block_building(ctx, str, "O", at_nw_corner, &add_size); break;
return do_block_building(ctx, "O", at_nw_corner, &add_size);
case building_type::Bridge:
do_block_building(ctx, str, get_bridge_str(ctx.b), at_nw_corner,
return do_block_building(ctx, get_bridge_str(ctx.b), at_nw_corner,
&add_size);
break;
case building_type::Well:
str = "l"; break;
return "l";
case building_type::SiegeEngine:
do_block_building(ctx, str, get_siege_str(ctx.b), at_center);
break;
return do_block_building(ctx, get_siege_str(ctx.b), at_center);
case building_type::Workshop:
do_block_building(ctx, str, get_workshop_str(ctx.b), at_center);
break;
return do_block_building(ctx, get_workshop_str(ctx.b), at_center);
case building_type::Furnace:
do_block_building(ctx, str, get_furnace_str(ctx.b), at_center);
break;
return do_block_building(ctx, get_furnace_str(ctx.b), at_center);
case building_type::WindowGlass:
str = "y"; break;
return "y";
case building_type::WindowGem:
str = "Y"; break;
return "Y";
case building_type::Construction:
str = get_construction_str(ctx.b); break;
return get_construction_str(ctx.b);
case building_type::Shop:
do_block_building(ctx, str, "z", at_center);
break;
return do_block_building(ctx, "z", at_center);
case building_type::AnimalTrap:
str = "m"; break;
return "m";
case building_type::Chain:
str = "v"; break;
return "v";
case building_type::Cage:
str = "j"; break;
return "j";
case building_type::TradeDepot:
do_block_building(ctx, str, "D", at_center); break;
return do_block_building(ctx, "D", at_center);
case building_type::Trap:
str = get_trap_str(ctx.b); break;
return get_trap_str(ctx.b);
case building_type::ScrewPump:
do_block_building(ctx, str, get_screw_pump_str(ctx.b), at_se_corner);
break;
return do_block_building(ctx, get_screw_pump_str(ctx.b), at_se_corner);
case building_type::WaterWheel:
do_block_building(ctx, str, get_water_wheel_str(ctx.b), at_center);
break;
return do_block_building(ctx, get_water_wheel_str(ctx.b), at_center);
case building_type::Windmill:
do_block_building(ctx, str, "Mm", at_center); break;
return do_block_building(ctx, "Mm", at_center);
case building_type::GearAssembly:
str = "Mg"; break;
return "Mg";
case building_type::AxleHorizontal:
do_block_building(ctx, str, get_axle_str(ctx.b), at_nw_corner,
&add_size);
break;
return do_block_building(ctx, get_axle_str(ctx.b), at_nw_corner,
&add_size);
case building_type::AxleVertical:
str = "Mv"; break;
return "Mv";
case building_type::Rollers:
do_block_building(ctx, str, get_roller_str(ctx.b), at_nw_corner,
&add_size);
break;
return do_block_building(ctx, get_roller_str(ctx.b), at_nw_corner,
&add_size);
case building_type::Support:
str = "S"; break;
return "S";
case building_type::ArcheryTarget:
str = "A"; break;
return "A";
case building_type::TractionBench:
str = "R"; break;
return "R";
case building_type::Hatch:
str = "H"; break;
return "H";
case building_type::Slab:
//how to mine alt key?!?
//alt+s
str = "~"; break;
return "~";
case building_type::NestBox:
str = "N"; break;
return "N";
case building_type::Hive:
//alt+h
str = "~"; break;
return "~";
case building_type::GrateWall:
str = "W"; break;
return "W";
case building_type::GrateFloor:
str = "G"; break;
return "G";
case building_type::BarsVertical:
str = "B"; break;
return "B";
case building_type::BarsFloor:
//alt+b
str = "~"; break;
return "~";
default:
str = if_pretty(ctx, "~");
break;
return "~";
}
if (add_size)
str.append(get_expansion_str(ctx.b));
}
static void get_tile_place(const df::coord &pos, const tile_context &ctx,
string &str) {
if (!ctx.b || ctx.b->getType() != building_type::Stockpile)
return;
// returns "~" if keys is NULL; otherwise returns the keys with the building
// dimensions in the expansion syntax
static const char * add_expansion_syntax(const tile_context &ctx,
const char *keys) {
if (!keys)
return "~";
std::ostringstream s;
pair<uint32_t, uint32_t> size = get_building_size(ctx.b);
s << keys << "(" << size.first << "x" << size.second << ")";
return cache(s.str().c_str());
}
if (ctx.b->x1 != static_cast<int32_t>(pos.x)
|| ctx.b->y1 != static_cast<int32_t>(pos.y)) {
str = if_pretty(ctx, "`");
return;
static const char * get_tile_build(const df::coord &pos,
const tile_context &ctx) {
if (!ctx.b || ctx.b->getType() == building_type::Stockpile) {
return NULL;
}
bool add_size = false;
const char *keys = get_build_keys(pos, ctx, add_size);
if (!add_size)
return keys;
return add_expansion_syntax(ctx, keys);
}
static const char * get_place_keys(const tile_context &ctx) {
df::building_stockpilest* sp =
virtual_cast<df::building_stockpilest>(ctx.b);
if (!sp) {
str = "~";
return;
}
return NULL;
}
switch (sp->settings.flags.whole) {
case df::stockpile_group_set::mask_animals: return "a";
case df::stockpile_group_set::mask_food: return "f";
case df::stockpile_group_set::mask_furniture: return "u";
case df::stockpile_group_set::mask_corpses: return "y";
case df::stockpile_group_set::mask_refuse: return "r";
case df::stockpile_group_set::mask_wood: return "w";
case df::stockpile_group_set::mask_stone: return "s";
case df::stockpile_group_set::mask_gems: return "e";
case df::stockpile_group_set::mask_bars_blocks: return "b";
case df::stockpile_group_set::mask_cloth: return "h";
case df::stockpile_group_set::mask_leather: return "l";
case df::stockpile_group_set::mask_ammo: return "z";
case df::stockpile_group_set::mask_coins: return "n";
case df::stockpile_group_set::mask_finished_goods: return "g";
case df::stockpile_group_set::mask_weapons: return "p";
case df::stockpile_group_set::mask_armor: return "d";
default: // TODO: handle stockpiles with multiple types
return NULL;
}
}
static const char * get_tile_place(const df::coord &pos,
const tile_context &ctx) {
if (!ctx.b || ctx.b->getType() != building_type::Stockpile)
return NULL;
switch (sp->settings.flags.whole)
{
case df::stockpile_group_set::mask_animals: str = "a"; break;
case df::stockpile_group_set::mask_food: str = "f"; break;
case df::stockpile_group_set::mask_furniture: str = "u"; break;
case df::stockpile_group_set::mask_corpses: str = "y"; break;
case df::stockpile_group_set::mask_refuse: str = "r"; break;
case df::stockpile_group_set::mask_wood: str = "w"; break;
case df::stockpile_group_set::mask_stone: str = "s"; break;
case df::stockpile_group_set::mask_gems: str = "e"; break;
case df::stockpile_group_set::mask_bars_blocks: str = "b"; break;
case df::stockpile_group_set::mask_cloth: str = "h"; break;
case df::stockpile_group_set::mask_leather: str = "l"; break;
case df::stockpile_group_set::mask_ammo: str = "z"; break;
case df::stockpile_group_set::mask_coins: str = "n"; break;
case df::stockpile_group_set::mask_finished_goods: str = "g"; break;
case df::stockpile_group_set::mask_weapons: str = "p"; break;
case df::stockpile_group_set::mask_armor: str = "d"; break;
default: // multiple stockpile types
str = "~";
return;
if (ctx.b->x1 != static_cast<int32_t>(pos.x)
|| ctx.b->y1 != static_cast<int32_t>(pos.y)) {
return if_pretty(ctx, "`");
}
str.append(get_expansion_str(ctx.b));
return add_expansion_syntax(ctx, get_place_keys(ctx));
}
static void get_tile_query(const df::coord &, const tile_context &ctx,
string &str) {
if (ctx.b && ctx.b->is_room)
str = "r+";
static const char * get_tile_query(const df::coord &, const tile_context &ctx) {
if (!ctx.b || !ctx.b->is_room)
return NULL;
return "r+";
}
static bool create_output_dir(color_ostream &out,
@ -613,22 +630,22 @@ static bool get_filename(string &fname,
return true;
}
typedef map<int16_t /* x */, string> bp_row;
typedef map<int16_t /* x */, const char *> bp_row;
typedef map<int16_t /* y */, bp_row> bp_area;
typedef map<int16_t /* z */, bp_area> bp_volume;
static const bp_area NEW_AREA;
static const bp_row NEW_ROW;
typedef void (get_tile_fn)(const df::coord &pos, const tile_context &ctx,
string &str);
typedef const char * (get_tile_fn)(const df::coord &pos,
const tile_context &ctx);
typedef void (init_ctx_fn)(const df::coord &pos, tile_context &ctx);
struct blueprint_processor {
bp_volume mapdata;
string phase;
get_tile_fn *get_tile;
init_ctx_fn *init_ctx;
const string phase;
get_tile_fn * const get_tile;
init_ctx_fn * const init_ctx;
blueprint_processor(const string &phase, get_tile_fn *get_tile,
init_ctx_fn *init_ctx = NULL)
: phase(phase), get_tile(get_tile), init_ctx(init_ctx) { }
@ -674,10 +691,10 @@ static void write_pretty(ofstream &ofile, const blueprint_options &opts,
if (area && area->count(y))
row = &area->at(y);
for (int16_t x = 0; x < opts.width; ++x) {
const string *tile = NULL;
const char *tile = NULL;
if (row && row->count(x))
tile = &row->at(x);
ofile << (tile ? *tile : " ") << ",";
tile = row->at(x);
ofile << (tile ? tile : " ") << ",";
}
ofile << "#" << endl;
}
@ -758,9 +775,8 @@ static bool do_transform(color_ostream &out,
for (blueprint_processor &processor : processors) {
if (processor.init_ctx)
processor.init_ctx(pos, ctx);
string tile_str;
processor.get_tile(pos, ctx, tile_str);
if (!tile_str.empty()) {
const char *tile_str = processor.get_tile(pos, ctx);
if (tile_str) {
// ensure our z-index is in the order we want to write
auto area = processor.mapdata.emplace(abs(z - start.z),
NEW_AREA);
@ -890,7 +906,9 @@ static bool do_blueprint(color_ostream &out,
if (end.z < -1)
end.z = -1;
return do_transform(out, start, end, options, files);
bool ok = do_transform(out, start, end, options, files);
cache(NULL);
return ok;
}
// entrypoint when called from Lua. returns the names of the generated files