From 42e04fc6ece778e1433191592e07ce8307640519 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 10 Sep 2021 14:19:50 -0700 Subject: [PATCH] use const char *, not std::string for efficiency so we can actually process large maps without OOMing --- plugins/blueprint.cpp | 336 ++++++++++++++++++++++-------------------- 1 file changed, 177 insertions(+), 159 deletions(-) diff --git a/plugins/blueprint.cpp b/plugins/blueprint.cpp index 63e721bde..1e70f0879 100644 --- a/plugins/blueprint.cpp +++ b/plugins/blueprint.cpp @@ -113,55 +113,72 @@ struct tile_context { df::building* b = NULL; }; -static pair get_building_size(df::building *b) { - return pair(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 _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 get_building_size(df::building *b) { + return pair(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(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(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(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(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(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(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(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(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(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(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 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(pos.x) == ctx.b->x1 && static_cast(pos.y) == ctx.b->y1; bool at_se_corner = static_cast(pos.x) == ctx.b->x2 && static_cast(pos.y) == ctx.b->y2; bool at_center = static_cast(pos.x) == ctx.b->centerx && static_cast(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 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(pos.x) - || ctx.b->y1 != static_cast(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(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(pos.x) + || ctx.b->y1 != static_cast(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 bp_row; +typedef map bp_row; typedef map bp_area; typedef map 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