overhaul serialization; persist item filters

develop
Myk Taylor 2023-02-22 15:08:11 -08:00
parent 60de4619a2
commit 4cc262c796
No known key found for this signature in database
11 changed files with 264 additions and 86 deletions

@ -2,12 +2,15 @@ project(buildingplan)
set(COMMON_HDRS
buildingplan.h
buildingtypekey.h
defaultitemfilters.h
itemfilter.h
plannedbuilding.h
)
set_source_files_properties(${COMMON_HDRS} PROPERTIES HEADER_FILE_ONLY TRUE)
dfhack_plugin(buildingplan
buildingplan.cpp buildingplan_cycle.cpp itemfilter.cpp plannedbuilding.cpp
buildingplan.cpp buildingplan_cycle.cpp buildingtypekey.cpp
defaultitemfilters.cpp itemfilter.cpp plannedbuilding.cpp
${COMMON_HDRS}
LINK_LIBRARIES lua)

@ -1,5 +1,7 @@
#include "plannedbuilding.h"
#include "buildingplan.h"
#include "buildingtypekey.h"
#include "defaultitemfilters.h"
#include "plannedbuilding.h"
#include "Debug.h"
#include "LuaTools.h"
@ -29,6 +31,7 @@ namespace DFHack {
}
static const string CONFIG_KEY = string(plugin_name) + "/config";
const string FILTER_CONFIG_KEY = string(plugin_name) + "/filter";
const string BLD_CONFIG_KEY = string(plugin_name) + "/building";
int get_config_val(PersistentDataItem &c, int index) {
@ -47,35 +50,11 @@ void set_config_bool(PersistentDataItem &c, int index, bool value) {
set_config_val(c, index, value ? 1 : 0);
}
// building type, subtype, custom
typedef std::tuple<df::building_type, int16_t, int32_t> BuildingTypeKey;
// rotates a size_t value left by count bits
// assumes count is not 0 or >= size_t_bits
// replace this with std::rotl when we move to C++20
static std::size_t rotl_size_t(size_t val, uint32_t count)
{
static const int size_t_bits = CHAR_BIT * sizeof(std::size_t);
return val << count | val >> (size_t_bits - count);
}
struct BuildingTypeKeyHash {
std::size_t operator() (const BuildingTypeKey & key) const {
// cast first param to appease gcc-4.8, which is missing the enum
// specializations for std::hash
std::size_t h1 = std::hash<int32_t>()(static_cast<int32_t>(std::get<0>(key)));
std::size_t h2 = std::hash<int16_t>()(std::get<1>(key));
std::size_t h3 = std::hash<int32_t>()(std::get<2>(key));
return h1 ^ rotl_size_t(h2, 8) ^ rotl_size_t(h3, 16);
}
};
static PersistentDataItem config;
// for use in counting available materials for the UI
static unordered_map<BuildingTypeKey, vector<df::job_item *>, BuildingTypeKeyHash> job_item_cache;
static unordered_map<BuildingTypeKey, vector<const df::job_item *>, BuildingTypeKeyHash> job_item_cache;
static unordered_map<BuildingTypeKey, HeatSafety, BuildingTypeKeyHash> cur_heat_safety;
static unordered_map<BuildingTypeKey, vector<ItemFilter>, BuildingTypeKeyHash> cur_item_filters;
static unordered_map<BuildingTypeKey, DefaultItemFilters, BuildingTypeKeyHash> cur_item_filters;
// building id -> PlannedBuilding
static unordered_map<int32_t, PlannedBuilding> planned_buildings;
// vector id -> filter bucket -> queue of (building id, job_item index)
@ -133,7 +112,7 @@ static int get_num_filters(color_ostream &out, BuildingTypeKey key) {
return num_filters;
}
static vector<df::job_item *> & get_job_items(color_ostream &out, BuildingTypeKey key) {
static const vector<const df::job_item *> & get_job_items(color_ostream &out, BuildingTypeKey key) {
if (job_item_cache.count(key))
return job_item_cache[key];
const int num_filters = get_num_filters(out, key);
@ -169,13 +148,11 @@ static HeatSafety get_heat_safety_filter(const BuildingTypeKey &key) {
return HEAT_SAFETY_ANY;
}
static vector<ItemFilter> & get_item_filters(color_ostream &out, const BuildingTypeKey &key) {
static DefaultItemFilters & get_item_filters(color_ostream &out, const BuildingTypeKey &key) {
if (cur_item_filters.count(key))
return cur_item_filters[key];
vector<ItemFilter> &filters = cur_item_filters[key];
filters.resize(get_job_items(out, key).size());
return filters;
return cur_item_filters.at(key);
cur_item_filters.emplace(key, DefaultItemFilters(out, key, get_job_items(out, key)));
return cur_item_filters.at(key);
}
static command_result do_command(color_ostream &out, vector<string> &parameters);
@ -258,6 +235,14 @@ DFhackCExport command_result plugin_load_data (color_ostream &out) {
DEBUG(status,out).print("loading persisted state\n");
clear_state(out);
vector<PersistentDataItem> filter_configs;
World::GetPersistentData(&filter_configs, FILTER_CONFIG_KEY);
for (auto &cfg : filter_configs) {
BuildingTypeKey key = DefaultItemFilters::getKey(cfg);
cur_item_filters.emplace(key, DefaultItemFilters(out, cfg, get_job_items(out, key)));
}
vector<PersistentDataItem> building_configs;
World::GetPersistentData(&building_configs, BLD_CONFIG_KEY);
const size_t num_building_configs = building_configs.size();
@ -265,13 +250,17 @@ DFhackCExport command_result plugin_load_data (color_ostream &out) {
PlannedBuilding pb(out, building_configs[idx]);
df::building *bld = df::building::find(pb.id);
if (!bld) {
WARN(status).print("cannot find building %d; halting load\n", pb.id);
INFO(status).print("building %d no longer exists; skipping\n", pb.id);
pb.remove(out);
continue;
}
BuildingTypeKey key(bld->getType(), bld->getSubtype(), bld->getCustomType());
if (pb.item_filters.size() != get_item_filters(out, key).size())
if (pb.item_filters.size() != get_item_filters(out, key).getItemFilters().size()) {
WARN(status).print("loaded state for building %d doesn't match world\n", pb.id);
else
registerPlannedBuilding(out, pb);
pb.remove(out);
continue;
}
registerPlannedBuilding(out, pb);
}
return CR_OK;
@ -352,7 +341,7 @@ static string getBucket(const df::job_item & ji) {
}
// get a list of item vectors that we should search for matches
vector<df::job_item_vector_id> getVectorIds(color_ostream &out, df::job_item *job_item) {
vector<df::job_item_vector_id> getVectorIds(color_ostream &out, const df::job_item *job_item) {
std::vector<df::job_item_vector_id> ret;
// if the filter already has the vector_id set to something specific, use it
@ -525,7 +514,7 @@ static bool addPlannedBuilding(color_ostream &out, df::building *bld) {
bld->getCustomType()))
return false;
BuildingTypeKey key(bld->getType(), bld->getSubtype(), bld->getCustomType());
PlannedBuilding pb(out, bld, get_heat_safety_filter(key), get_item_filters(out, key));
PlannedBuilding pb(out, bld, get_heat_safety_filter(key), get_item_filters(out, key).getItemFilters());
return registerPlannedBuilding(out, pb);
}
@ -549,7 +538,7 @@ static int scanAvailableItems(color_ostream &out, df::building_type type, int16_
auto &job_items = get_job_items(out, key);
if (index < 0 || job_items.size() <= (size_t)index)
return 0;
auto &item_filters = get_item_filters(out, key);
auto &item_filters = get_item_filters(out, key).getItemFilters();
auto &jitem = job_items[index];
auto vector_ids = getVectorIds(out, jitem);
@ -595,7 +584,13 @@ static int countAvailableItems(color_ostream &out, df::building_type type, int16
}
static bool hasFilter(color_ostream &out, df::building_type type, int16_t subtype, int32_t custom, int index) {
DEBUG(status,out).print("entering hasFilter\n");
TRACE(status,out).print("entering hasFilter\n");
BuildingTypeKey key(type, subtype, custom);
auto &filters = get_item_filters(out, key);
for (auto &filter : filters.getItemFilters()) {
if (filter.isEmpty())
return true;
}
return false;
}

@ -13,6 +13,7 @@
typedef std::deque<std::pair<int32_t, int>> Bucket;
typedef std::map<df::job_item_vector_id, std::map<std::string, Bucket>> Tasks;
extern const std::string FILTER_CONFIG_KEY;
extern const std::string BLD_CONFIG_KEY;
enum ConfigValues {
@ -22,6 +23,12 @@ enum ConfigValues {
CONFIG_BARS = 4,
};
enum FilterConfigValues {
FILTER_CONFIG_TYPE = 0,
FILTER_CONFIG_SUBTYPE = 1,
FILTER_CONFIG_CUSTOM = 2,
};
enum BuildingConfigValues {
BLD_CONFIG_ID = 0,
BLD_CONFIG_HEAT = 1,
@ -38,8 +45,8 @@ bool get_config_bool(DFHack::PersistentDataItem &c, int index);
void set_config_val(DFHack::PersistentDataItem &c, int index, int value);
void set_config_bool(DFHack::PersistentDataItem &c, int index, bool value);
std::vector<df::job_item_vector_id> getVectorIds(DFHack::color_ostream &out, df::job_item *job_item);
std::vector<df::job_item_vector_id> getVectorIds(DFHack::color_ostream &out, const df::job_item *job_item);
bool itemPassesScreen(df::item * item);
bool matchesFilters(df::item * item, df::job_item * job_item, HeatSafety heat, const ItemFilter &item_filter);
bool matchesFilters(df::item * item, const df::job_item * job_item, HeatSafety heat, const ItemFilter &item_filter);
bool isJobReady(DFHack::color_ostream &out, const std::vector<df::job_item *> &jitems);
void finalizeBuilding(DFHack::color_ostream &out, df::building *bld);

@ -46,7 +46,7 @@ bool itemPassesScreen(df::item * item) {
&& !item->isAssignedToStockpile();
}
bool matchesFilters(df::item * item, df::job_item * job_item, HeatSafety heat, const ItemFilter &item_filter) {
bool matchesFilters(df::item * item, const df::job_item * job_item, HeatSafety heat, const ItemFilter &item_filter) {
// check the properties that are not checked by Job::isSuitableItem()
if (job_item->item_type > -1 && job_item->item_type != item->getType())
return false;

@ -0,0 +1,59 @@
#include "buildingplan.h"
#include "buildingtypekey.h"
#include "Debug.h"
#include "MiscUtils.h"
using std::string;
using std::vector;
namespace DFHack {
DBG_EXTERN(buildingplan, status);
}
using namespace DFHack;
// building type, subtype, custom
BuildingTypeKey::BuildingTypeKey(df::building_type type, int16_t subtype, int32_t custom)
: tuple(type, subtype, custom) { }
static BuildingTypeKey deserialize(color_ostream &out, const std::string &serialized) {
vector<string> key_parts;
split_string(&key_parts, serialized, ",");
if (key_parts.size() != 3) {
WARN(status,out).print("invalid key_str: '%s'\n", serialized.c_str());
return BuildingTypeKey(df::building_type::NONE, -1, -1);
}
return BuildingTypeKey((df::building_type)string_to_int(key_parts[0]),
string_to_int(key_parts[1]), string_to_int(key_parts[2]));
}
BuildingTypeKey::BuildingTypeKey(color_ostream &out, const std::string &serialized)
:tuple(deserialize(out, serialized)) { }
string BuildingTypeKey::serialize() const {
std::ostringstream ser;
ser << std::get<0>(*this) << ",";
ser << std::get<1>(*this) << ",";
ser << std::get<2>(*this);
return ser.str();
}
// rotates a size_t value left by count bits
// assumes count is not 0 or >= size_t_bits
// replace this with std::rotl when we move to C++20
static std::size_t rotl_size_t(size_t val, uint32_t count)
{
static const int size_t_bits = CHAR_BIT * sizeof(std::size_t);
return val << count | val >> (size_t_bits - count);
}
std::size_t BuildingTypeKeyHash::operator() (const BuildingTypeKey & key) const {
// cast first param to appease gcc-4.8, which is missing the enum
// specializations for std::hash
std::size_t h1 = std::hash<int32_t>()(static_cast<int32_t>(std::get<0>(key)));
std::size_t h2 = std::hash<int16_t>()(std::get<1>(key));
std::size_t h3 = std::hash<int32_t>()(std::get<2>(key));
return h1 ^ rotl_size_t(h2, 8) ^ rotl_size_t(h3, 16);
}

@ -0,0 +1,22 @@
#pragma once
#include "df/building_type.h"
#include <tuple>
#include <string>
namespace DFHack {
class color_ostream;
}
// building type, subtype, custom
struct BuildingTypeKey : public std::tuple<df::building_type, int16_t, int32_t> {
BuildingTypeKey(df::building_type type, int16_t subtype, int32_t custom);
BuildingTypeKey(DFHack::color_ostream &out, const std::string & serialized);
std::string serialize() const;
};
struct BuildingTypeKeyHash {
std::size_t operator() (const BuildingTypeKey & key) const;
};

@ -0,0 +1,60 @@
#include "defaultitemfilters.h"
#include "Debug.h"
#include "MiscUtils.h"
#include "modules/World.h"
namespace DFHack {
DBG_EXTERN(buildingplan, status);
}
using std::string;
using std::vector;
using namespace DFHack;
BuildingTypeKey DefaultItemFilters::getKey(PersistentDataItem &filter_config) {
return BuildingTypeKey(
(df::building_type)get_config_val(filter_config, FILTER_CONFIG_TYPE),
get_config_val(filter_config, FILTER_CONFIG_SUBTYPE),
get_config_val(filter_config, FILTER_CONFIG_CUSTOM));
}
DefaultItemFilters::DefaultItemFilters(color_ostream &out, BuildingTypeKey key, const std::vector<const df::job_item *> &jitems)
: key(key) {
DEBUG(status,out).print("creating persistent data for filter key %d,%d,%d\n",
std::get<0>(key), std::get<1>(key), std::get<2>(key));
filter_config = World::AddPersistentData(FILTER_CONFIG_KEY);
set_config_val(filter_config, FILTER_CONFIG_TYPE, std::get<0>(key));
set_config_val(filter_config, FILTER_CONFIG_SUBTYPE, std::get<1>(key));
set_config_val(filter_config, FILTER_CONFIG_CUSTOM, std::get<2>(key));
item_filters.resize(jitems.size());
filter_config.val() = serialize_item_filters(item_filters);
}
DefaultItemFilters::DefaultItemFilters(color_ostream &out, PersistentDataItem &filter_config, const std::vector<const df::job_item *> &jitems)
: key(getKey(filter_config)), filter_config(filter_config) {
auto &serialized = filter_config.val();
DEBUG(status,out).print("deserializing item filters for key %d,%d,%d: %s\n",
std::get<0>(key), std::get<1>(key), std::get<2>(key), serialized.c_str());
std::vector<ItemFilter> filters = deserialize_item_filters(out, serialized);
if (filters.size() != jitems.size()) {
WARN(status,out).print("ignoring invalid filters_str for key %d,%d,%d: '%s'\n",
std::get<0>(key), std::get<1>(key), std::get<2>(key), serialized.c_str());
item_filters.resize(jitems.size());
} else
item_filters = filters;
}
void DefaultItemFilters::setItemFilter(DFHack::color_ostream &out, const ItemFilter &filter, int index) {
if (item_filters.size() <= index) {
WARN(status,out).print("invalid index for filter key %d,%d,%d: %d\n",
std::get<0>(key), std::get<1>(key), std::get<2>(key), index);
return;
}
item_filters[index] = filter;
filter_config.val() = serialize_item_filters(item_filters);
DEBUG(status,out).print("updated item filter and persisted for key %d,%d,%d: %s\n",
std::get<0>(key), std::get<1>(key), std::get<2>(key), filter_config.val().c_str());
}

@ -0,0 +1,24 @@
#pragma once
#include "buildingplan.h"
#include "buildingtypekey.h"
#include "modules/Persistence.h"
class DefaultItemFilters {
public:
static BuildingTypeKey getKey(DFHack::PersistentDataItem &filter_config);
const BuildingTypeKey key;
DefaultItemFilters(DFHack::color_ostream &out, BuildingTypeKey key, const std::vector<const df::job_item *> &jitems);
DefaultItemFilters(DFHack::color_ostream &out, DFHack::PersistentDataItem &filter_config, const std::vector<const df::job_item *> &jitems);
void setItemFilter(DFHack::color_ostream &out, const ItemFilter &filter, int index);
const std::vector<ItemFilter> & getItemFilters() const { return item_filters; }
private:
DFHack::PersistentDataItem filter_config;
std::vector<ItemFilter> item_filters;
};

@ -4,12 +4,15 @@
#include "df/item.h"
using namespace DFHack;
namespace DFHack {
DBG_EXTERN(buildingplan, status);
}
using std::string;
using std::vector;
using namespace DFHack;
ItemFilter::ItemFilter() {
clear();
}
@ -22,7 +25,7 @@ void ItemFilter::clear() {
materials.clear();
}
bool ItemFilter::isEmpty() {
bool ItemFilter::isEmpty() const {
return min_quality == df::item_quality::Ordinary
&& max_quality == df::item_quality::Masterful
&& !decorated_only
@ -30,7 +33,7 @@ bool ItemFilter::isEmpty() {
&& materials.empty();
}
static bool deserializeMaterialMask(std::string ser, df::dfhack_material_category mat_mask) {
static bool deserializeMaterialMask(string ser, df::dfhack_material_category mat_mask) {
if (ser.empty())
return true;
@ -41,11 +44,11 @@ static bool deserializeMaterialMask(std::string ser, df::dfhack_material_categor
return true;
}
static bool deserializeMaterials(std::string ser, std::vector<DFHack::MaterialInfo> &materials) {
static bool deserializeMaterials(string ser, vector<DFHack::MaterialInfo> &materials) {
if (ser.empty())
return true;
std::vector<std::string> mat_names;
vector<string> mat_names;
split_string(&mat_names, ser, ",");
for (auto m = mat_names.begin(); m != mat_names.end(); m++) {
DFHack::MaterialInfo material;
@ -58,13 +61,13 @@ static bool deserializeMaterials(std::string ser, std::vector<DFHack::MaterialIn
return true;
}
ItemFilter::ItemFilter(std::string serialized) {
ItemFilter::ItemFilter(color_ostream &out, string serialized) {
clear();
std::vector<std::string> tokens;
vector<string> tokens;
split_string(&tokens, serialized, "/");
if (tokens.size() != 5) {
DEBUG(status).print("invalid ItemFilter serialization: '%s'", serialized.c_str());
DEBUG(status,out).print("invalid ItemFilter serialization: '%s'", serialized.c_str());
return;
}
@ -77,7 +80,7 @@ ItemFilter::ItemFilter(std::string serialized) {
}
// format: mat,mask,elements/materials,list/minq/maxq/decorated
std::string ItemFilter::serialize() const {
string ItemFilter::serialize() const {
std::ostringstream ser;
ser << bitfield_to_string(mat_mask, ",") << "/";
if (!materials.empty()) {
@ -124,15 +127,15 @@ void ItemFilter::setMaterialMask(uint32_t mask) {
mat_mask.whole = mask;
}
void ItemFilter::setMaterials(const std::vector<DFHack::MaterialInfo> &materials) {
void ItemFilter::setMaterials(const vector<DFHack::MaterialInfo> &materials) {
this->materials = materials;
}
std::string ItemFilter::getMinQuality() const {
string ItemFilter::getMinQuality() const {
return ENUM_KEY_STR(item_quality, min_quality);
}
std::string ItemFilter::getMaxQuality() const {
string ItemFilter::getMaxQuality() const {
return ENUM_KEY_STR(item_quality, max_quality);
}
@ -144,10 +147,10 @@ uint32_t ItemFilter::getMaterialMask() const {
return mat_mask.whole;
}
static std::string material_to_string_fn(const MaterialInfo &m) { return m.toString(); }
static string material_to_string_fn(const MaterialInfo &m) { return m.toString(); }
std::vector<std::string> ItemFilter::getMaterials() const {
std::vector<std::string> descriptions;
vector<string> ItemFilter::getMaterials() const {
vector<string> descriptions;
transform_(materials, descriptions, material_to_string_fn);
if (descriptions.size() == 0)
@ -187,3 +190,23 @@ bool ItemFilter::matches(df::item *item) const {
return (materials.size() == 0) ? matchesMask(item_mat, mat_mask) : matches(item_mat);
}
vector<ItemFilter> deserialize_item_filters(color_ostream &out, const string &serialized) {
std::vector<ItemFilter> filters;
vector<string> filter_strs;
split_string(&filter_strs, serialized, ";");
for (auto &str : filter_strs) {
filters.emplace_back(out, str);
}
return filters;
}
string serialize_item_filters(const vector<ItemFilter> &filters) {
vector<string> strs;
for (auto &filter : filters) {
strs.emplace_back(filter.serialize());
}
return join_strings(";", strs);
}

@ -8,10 +8,10 @@
class ItemFilter {
public:
ItemFilter();
ItemFilter(std::string serialized);
ItemFilter(DFHack::color_ostream &out, std::string serialized);
void clear();
bool isEmpty();
bool isEmpty() const;
std::string serialize() const;
void setMinQuality(int quality);
@ -37,3 +37,6 @@ private:
df::dfhack_material_category mat_mask;
std::vector<DFHack::MaterialInfo> materials;
};
std::vector<ItemFilter> deserialize_item_filters(DFHack::color_ostream &out, const std::string &serialized);
std::string serialize_item_filters(const std::vector<ItemFilter> &filters);

@ -58,25 +58,14 @@ static vector<vector<df::job_item_vector_id>> deserialize_vector_ids(color_ostre
return ret;
}
static std::vector<ItemFilter> deserialize_item_filters(color_ostream &out, PersistentDataItem &bld_config) {
static std::vector<ItemFilter> get_item_filters(color_ostream &out, PersistentDataItem &bld_config) {
std::vector<ItemFilter> ret;
vector<string> rawstrs;
split_string(&rawstrs, bld_config.val(), "|");
if (rawstrs.size() < 2)
return ret;
const string &serialized = rawstrs[1];
DEBUG(status,out).print("deserializing item filters for building %d: %s\n",
get_config_val(bld_config, BLD_CONFIG_ID), serialized.c_str());
vector<string> filterstrs;
split_string(&filterstrs, serialized, ";");
for (auto &str : filterstrs) {
ret.emplace_back(str);
}
return ret;
return deserialize_item_filters(out, rawstrs[1]);
}
static string serialize(const vector<vector<df::job_item_vector_id>> &vector_ids, const vector<ItemFilter> &item_filters) {
@ -85,14 +74,7 @@ static string serialize(const vector<vector<df::job_item_vector_id>> &vector_ids
joined.emplace_back(join_strings(",", vec_list));
}
std::ostringstream out;
out << join_strings(";", joined) << "|";
joined.clear();
for (auto &filter : item_filters) {
joined.emplace_back(filter.serialize());
}
out << join_strings(";", joined);
out << join_strings(";", joined) << "|" << serialize_item_filters(item_filters);
return out.str();
}
@ -111,7 +93,7 @@ PlannedBuilding::PlannedBuilding(color_ostream &out, PersistentDataItem &bld_con
: id(get_config_val(bld_config, BLD_CONFIG_ID)),
vector_ids(deserialize_vector_ids(out, bld_config)),
heat_safety((HeatSafety)get_config_val(bld_config, BLD_CONFIG_HEAT)),
item_filters(deserialize_item_filters(out, bld_config)),
item_filters(get_item_filters(out, bld_config)),
bld_config(bld_config) { }
// Ensure the building still exists and is in a valid state. It can disappear