From 7f5aa4de62931d7c5dad2630103803d7ec14e208 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Tue, 10 Jan 2012 17:23:37 +0400 Subject: [PATCH] Support the most important job types in workflow. --- library/include/modules/Materials.h | 10 + library/modules/Items.cpp | 2 +- library/modules/Materials.cpp | 51 +++- library/xml | 2 +- plugins/jobutils.cpp | 9 +- plugins/workflow.cpp | 428 ++++++++++++++++++++++------ 6 files changed, 402 insertions(+), 100 deletions(-) diff --git a/library/include/modules/Materials.h b/library/include/modules/Materials.h index e1455c158..7bc56d1fc 100644 --- a/library/include/modules/Materials.h +++ b/library/include/modules/Materials.h @@ -51,6 +51,7 @@ namespace df struct job_item; union job_material_category; + union dfhack_material_category; union job_item_flags1; union job_item_flags2; union job_item_flags3; @@ -118,11 +119,20 @@ namespace DFHack df::craft_material_class getCraftClass(); + bool matches(const MaterialInfo &mat) + { + if (!mat.isValid()) return true; + return (type == mat.type) && + (mat.index == -1 || index == mat.index); + } + bool matches(const df::job_material_category &cat); + bool matches(const df::dfhack_material_category &cat); bool matches(const df::job_item &item); }; DFHACK_EXPORT bool parseJobMaterialCategory(df::job_material_category *cat, const std::string &token); + DFHACK_EXPORT bool parseJobMaterialCategory(df::dfhack_material_category *cat, const std::string &token); inline bool operator== (const MaterialInfo &a, const MaterialInfo &b) { return a.type == b.type && a.index == b.index; diff --git a/library/modules/Items.cpp b/library/modules/Items.cpp index fb2ae6655..8ec4e2301 100644 --- a/library/modules/Items.cpp +++ b/library/modules/Items.cpp @@ -187,7 +187,7 @@ bool ItemTypeInfo::matches(const df::job_item &item, MaterialInfo *mat) using namespace df::enums::item_type; if (!isValid()) - return mat ? mat->matches(item) : true; + return mat ? mat->matches(item) : false; df::job_item_flags1 ok1, mask1, item_ok1, item_mask1; df::job_item_flags2 ok2, mask2, item_ok2, item_mask2; diff --git a/library/modules/Materials.cpp b/library/modules/Materials.cpp index 716717230..cc9102ae5 100644 --- a/library/modules/Materials.cpp +++ b/library/modules/Materials.cpp @@ -52,6 +52,7 @@ using namespace std; #include "df/job_item.h" #include "df/job_material_category.h" +#include "df/dfhack_material_category.h" #include "df/matter_state.h" #include "df/material_vec_ref.h" @@ -314,8 +315,9 @@ bool MaterialInfo::matches(const df::job_material_category &cat) if (!material) return false; - using namespace df::enums::material_flags; #define TEST(bit,flag) if (cat.bits.bit && material->flags.is_set(flag)) return true; + + using namespace df::enums::material_flags; TEST(plant, STRUCTURAL_PLANT_MAT); TEST(wood, WOOD); TEST(cloth, THREAD_PLANT); @@ -329,13 +331,37 @@ bool MaterialInfo::matches(const df::job_material_category &cat) TEST(horn, HORN); TEST(pearl, PEARL); TEST(yarn, YARN); -#undef TEST return false; } +bool MaterialInfo::matches(const df::dfhack_material_category &cat) +{ + if (!material) + return false; + + df::job_material_category mc; + mc.whole = cat.whole; + if (matches(mc)) + return true; + + using namespace df::enums::material_flags; + using namespace df::enums::inorganic_flags; + TEST(metal, IS_METAL); + TEST(stone, IS_STONE); + if (cat.bits.stone && type == 0 && index == -1) + return true; + if (cat.bits.sand && inorganic && inorganic->flags.is_set(SOIL_SAND)) + return true; + TEST(glass, IS_GLASS); + if (cat.bits.clay && linear_index(material->reaction_product.id, std::string("FIRED_MAT")) >= 0) + return true; +} + +#undef TEST + bool MaterialInfo::matches(const df::job_item &item) { - if (!isValid()) return true; + if (!isValid()) return false; df::job_item_flags1 ok1, mask1; getMatchBits(ok1, mask1); @@ -449,6 +475,25 @@ bool DFHack::parseJobMaterialCategory(df::job_material_category *cat, const std: return true; } +bool DFHack::parseJobMaterialCategory(df::dfhack_material_category *cat, const std::string &token) +{ + cat->whole = 0; + + std::vector items; + split_string(&items, toLower(token), ",", true); + + for (unsigned i = 0; i < items.size(); i++) + { + int id = findBitfieldField(*cat, items[i]); + if (id < 0) + return false; + + cat->whole |= (1 << id); + } + + return true; +} + Module* DFHack::createMaterials() { return new Materials(); diff --git a/library/xml b/library/xml index f7903623e..9bbec980c 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit f7903623ec2f69759debd2974b037463cc46efff +Subproject commit 9bbec980c28d296ce80813cb1bfc4dd31c19266d diff --git a/plugins/jobutils.cpp b/plugins/jobutils.cpp index 29d86230c..8557f0102 100644 --- a/plugins/jobutils.cpp +++ b/plugins/jobutils.cpp @@ -268,7 +268,10 @@ static command_result job_duplicate(Core * c, vector & parameters) if (!job) return CR_FAILURE; - if (!job->misc_links.empty() || job->job_items.empty()) + if (!job->misc_links.empty() || + (job->job_items.empty() && + job->job_type != job_type::CollectSand && + job->job_type != job_type::CollectClay)) { c->con.printerr("Cannot duplicate job %s\n", ENUM_KEY_STR(job_type,job->job_type)); return CR_FAILURE; @@ -343,7 +346,7 @@ static command_result job_cmd(Core * c, vector & parameters) return CR_FAILURE; } - if (!iinfo.matches(*item, &minfo)) { + if (minfo.isValid() && !iinfo.matches(*item, &minfo)) { c->con.printerr("Material does not match the requirements.\n"); printJobDetails(c, job); return CR_FAILURE; @@ -387,7 +390,7 @@ static command_result job_cmd(Core * c, vector & parameters) return CR_FAILURE; } - if (!iinfo.matches(*item, &minfo)) { + if (iinfo.isValid() && !iinfo.matches(*item, &minfo)) { c->con.printerr("Item type does not match the requirements.\n"); printJobDetails(c, job); return CR_FAILURE; diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp index 464cf468d..a874cd45a 100644 --- a/plugins/workflow.cpp +++ b/plugins/workflow.cpp @@ -18,12 +18,19 @@ #include #include #include +#include #include #include #include #include #include #include +#include +#include +#include +#include +#include +#include using std::vector; using std::string; @@ -78,14 +85,30 @@ DFhackCExport command_result plugin_init (Core *c, std::vector & " - In addition, when any constraints on item amounts are set, repeat jobs\n" " that produce that kind of item are automatically suspended and resumed\n" " as the item amount goes above or below the limit. The gap specifies how\n" - " much below the limit the amount has to drop before jobs are resumed.\n" + " much below the limit the amount has to drop before jobs are resumed;\n" + " this is intended to reduce the frequency of jobs being toggled.\n" "Constraint examples:\n" - " workflow limit AMMO:ITEM_AMMO_BOLTS//WOOD,BONE 200 50\n" - " Keep wooden and bone bolts between 150 and 200.\n" + " workflow limit AMMO:ITEM_AMMO_BOLTS/METAL 1000 100\n" + " workflow limit AMMO:ITEM_AMMO_BOLTS/WOOD,BONE 200 50\n" + " Keep metal bolts within 900-1000, and wood/bone within 150-200.\n" + " workflow limit-count FOOD 120 30\n" " workflow limit-count DRINK 120 30\n" - " Keep the number of drink barrels between 90 and 120\n" - " workflow limit-count BIN 30\n" - " Make sure there are always 25-30 empty bins.\n" + " Keep the number of prepared food & drink stacks between 90 and 120\n" + " workflow limit BIN 30\n" + " workflow limit BARREL 30\n" + " workflow limit BOX/CLOTH,SILK,YARN 30\n" + " Make sure there are always 25-30 empty bins/barrels/bags.\n" + " workflow limit BAR//COAL 20\n" + " workflow limit BAR//COPPER 30\n" + " Make sure there are always 15-20 coal and 25-30 copper bars.\n" + " workflow limit-count POWDER_MISC/SAND 20\n" + " workflow limit-count BOULDER/CLAY 20\n" + " Collect 15-20 sand bags and clay boulders.\n" + " workflow limit POWDER_MISC//MUSHROOM_CUP_DIMPLE:MILL 100 20\n" + " Make sure there are always 80-100 units of dimple dye.\n" + " In order for this to work, you have to set the material of\n" + " the PLANT input on the Mill Plants job to MUSHROOM_CUP_DIMPLE\n" + " using the 'job item-material' command.\n" ) ); } @@ -131,6 +154,7 @@ struct ProtectedJob { bool live; df::building *holder; df::job *job_copy; + int reaction_id; df::job *actual_job; @@ -143,6 +167,7 @@ struct ProtectedJob { building_id = holder ? holder->id : -1; job_copy = cloneJobStruct(job); actual_job = job; + reaction_id = -1; } ~ProtectedJob() @@ -156,6 +181,7 @@ struct ProtectedJob { if (*job == *job_copy) return; + reaction_id = -1; deleteJobStruct(job_copy); job_copy = cloneJobStruct(job); } @@ -169,7 +195,7 @@ struct ItemConstraint { ItemTypeInfo item; MaterialInfo material; - df::job_material_category mat_mask; + df::dfhack_material_category mat_mask; int weight; std::vector jobs; @@ -238,8 +264,10 @@ static ProtectedJob *get_known(int id) static bool isSupportedJob(df::job *job) { return job->misc_links.empty() && - !job->job_items.empty() && - getJobHolder(job); + getJobHolder(job) && + (!job->job_items.empty() || + job->job_type == job_type::CollectClay || + job->job_type == job_type::CollectSand); } static void enumLiveJobs(std::map &rv) @@ -503,8 +531,18 @@ static ItemConstraint *get_constraint(Core *c, const std::string &str, Persisten if (item.subtype >= 0) weight += 10000; + df::dfhack_material_category mat_mask; + std::string maskstr = vector_get(tokens,1); + if (!maskstr.empty() && !parseJobMaterialCategory(&mat_mask, maskstr)) { + c->con.printerr("Cannot decode material mask: %s\n", maskstr.c_str()); + return NULL; + } + + if (mat_mask.whole != 0) + weight += 100; + MaterialInfo material; - std::string matstr = vector_get(tokens,1); + std::string matstr = vector_get(tokens,2); if (!matstr.empty() && (!material.find(matstr) || !material.isValid())) { c->con.printerr("Cannot find material: %s\n", matstr.c_str()); return NULL; @@ -513,21 +551,11 @@ static ItemConstraint *get_constraint(Core *c, const std::string &str, Persisten if (material.type >= 0) weight += (material.index >= 0 ? 5000 : 1000); - df::job_material_category mat_mask; - std::string maskstr = vector_get(tokens,2); - if (!maskstr.empty() && !parseJobMaterialCategory(&mat_mask, maskstr)) { - c->con.printerr("Cannot decode material mask: %s\n", maskstr.c_str()); - return NULL; - } - if (mat_mask.whole && material.isValid() && !material.matches(mat_mask)) { c->con.printerr("Material %s doesn't match mask %s\n", matstr.c_str(), maskstr.c_str()); return NULL; } - if (mat_mask.whole != 0) - weight += 100; - for (unsigned i = 0; i < constraints.size(); i++) { ItemConstraint *ct = constraints[i]; @@ -564,99 +592,241 @@ static void delete_constraint(Core *c, ItemConstraint *cv) delete cv; } -static void print_constraint(Core *c, ItemConstraint *cv, bool no_job = false, std::string prefix = "") +static void link_job_constraint(ProtectedJob *pj, df::item_type itype, int16_t isubtype, + df::dfhack_material_category mat_mask, + int16_t mat_type, int32_t mat_index) { - c->con << prefix << "Constraint " << cv->config.val() << ": " - << (cv->goalByCount() ? "count " : "amount ") - << cv->goalCount() << " (gap " << cv->goalGap() << ")" << endl; - - if (cv->item_count || cv->item_inuse) - c->con << prefix << " items: amount " << cv->item_amount << "; " - << cv->item_count << " stacks available, " - << cv->item_inuse << " in use." << endl; + MaterialInfo mat(mat_type, mat_index); - if (no_job) return; + for (unsigned i = 0; i < constraints.size(); i++) + { + ItemConstraint *ct = constraints[i]; - if (cv->jobs.empty()) - c->con.printerr(" (no jobs)\n"); + if (ct->item.type != itype || + (ct->item.subtype != -1 && ct->item.subtype != isubtype)) + continue; + if (!mat.matches(ct->material)) + continue; + if (ct->mat_mask.whole) + { + if (mat.isValid()) + { + if (!mat.matches(ct->mat_mask)) + continue; + } + else + { + if (!(mat_mask.whole & ct->mat_mask.whole)) + continue; + } + } - for (int i = 0; i < cv->jobs.size(); i++) - { - ProtectedJob *pj = cv->jobs[i]; - df::job *job = pj->actual_job; + if (linear_index(pj->constraints, ct) >= 0) + continue; - c->con << prefix << " job " << job->id << ": " - << ENUM_KEY_STR(job_type, job->job_type); - if (job->flags.bits.suspend) - c->con << " (suspended)"; - c->con << endl; + ct->jobs.push_back(pj); + pj->constraints.push_back(ct); } } -static void print_job(Core *c, ProtectedJob *pj) +static void compute_custom_job(ProtectedJob *pj, df::job *job) { - if (!pj) + if (pj->reaction_id < 0) + pj->reaction_id = linear_index(df::reaction::get_vector(), + &df::reaction::code, job->reaction_name); + + df::reaction *r = df::reaction::find(pj->reaction_id); + if (!r) return; - printJobDetails(c, pj->job_copy); + for (unsigned i = 0; i < r->products.size(); i++) + { + using namespace df::enums::reaction_product_item_flags; - for (int i = 0; i < pj->constraints.size(); i++) - print_constraint(c, pj->constraints[i], true, " "); + VIRTUAL_CAST_VAR(prod, df::reaction_product_itemst, r->products[i]); + if (!prod || prod->item_type < 0) + continue; + + MaterialInfo mat(prod); + + bool get_mat_prod = prod->flags.is_set(GET_MATERIAL_PRODUCT); + if (get_mat_prod || prod->flags.is_set(GET_MATERIAL_SAME)) + { + int reagent_idx = linear_index(r->reagents, &df::reaction_reagent::code, + prod->get_material.reagent_code); + if (reagent_idx < 0) + continue; + + int item_idx = linear_index(job->job_items, &df::job_item::reagent_index, reagent_idx); + if (item_idx >= 0) + mat.decode(job->job_items[item_idx]); + else + { + VIRTUAL_CAST_VAR(src, df::reaction_reagent_itemst, r->reagents[reagent_idx]); + if (!src) + continue; + mat.decode(src); + } + + if (get_mat_prod) + { + if (!mat.isValid()) + continue; + + int idx = linear_index(mat.material->reaction_product.id, + prod->get_material.product_code); + if (idx < 0) + continue; + + mat.decode(mat.material->reaction_product.material, idx); + } + } + + link_job_constraint(pj, prod->item_type, prod->item_subtype, + 0, mat.type, mat.index); + } } -static void map_job_constraints(Core *c) +static void guess_job_material(df::job *job, MaterialInfo &mat, df::dfhack_material_category &mat_mask) { - for (unsigned i = 0; i < constraints.size(); i++) - constraints[i]->jobs.clear(); + using namespace df::enums::job_type; - for (TKnownJobs::const_iterator it = known_jobs.begin(); it != known_jobs.end(); ++it) + if (job->job_type == PrepareMeal) + mat.decode(-1); + else + mat.decode(job); + + mat_mask.whole = job->material_category.whole; + + // Material from the job enum + const char *job_material = ENUM_ATTR(job_type, material, job->job_type); + if (job_material) { - it->second->constraints.clear(); + MaterialInfo info; + if (info.findBuiltin(job_material)) + mat = info; + else + parseJobMaterialCategory(&mat_mask, job_material); + } - if (!it->second->live) - continue; + // Material from the job reagent + if (!mat.isValid() && job->job_items.size() == 1) + { + mat.decode(job->job_items[0]); + + switch (job->job_items[0]->item_type) + { + case item_type::WOOD: + mat_mask.bits.wood = mat_mask.bits.wood2 = true; + break; + } + } +} - df::job *job = it->second->job_copy; +static void compute_job_outputs(Core *c, ProtectedJob *pj) +{ + using namespace df::enums::job_type; - df::item_type itype = ENUM_ATTR(job_type, item, job->job_type); - if (itype == item_type::NONE) - continue; + // Custom reactions handled in another function + df::job *job = pj->job_copy; - int16_t isubtype = job->item_subtype; + if (job->job_type == CustomReaction) + { + compute_custom_job(pj, job); + return; + } - int16_t mat_type = job->mat_type; - int32_t mat_index = job->mat_index; + // Item type & subtype + df::item_type itype = ENUM_ATTR(job_type, item, job->job_type); + int16_t isubtype = job->item_subtype; + if (itype == item_type::NONE) + return; - if (itype == item_type::FOOD) - mat_type = -1; + // Item material & material category + MaterialInfo mat; + df::dfhack_material_category mat_mask; + guess_job_material(job, mat, mat_mask); - if (mat_type == -1 && job->job_items.size() == 1) { - mat_type = job->job_items[0]->mat_type; - mat_index = job->job_items[0]->mat_index; + // Job-specific code + switch (job->job_type) + { + case SmeltOre: + if (mat.inorganic) + { + std::vector &ores = mat.inorganic->metal_ore.mat_index; + for (unsigned i = 0; i < ores.size(); i++) + link_job_constraint(pj, item_type::BAR, -1, 0, 0, ores[i]); } + return; - MaterialInfo mat(mat_type, mat_index); + case ExtractMetalStrands: + if (mat.inorganic) + { + std::vector &threads = mat.inorganic->thread_metal.mat_index; + for (unsigned i = 0; i < threads.size(); i++) + link_job_constraint(pj, item_type::THREAD, -1, 0, 0, threads[i]); + } + return; - for (unsigned i = 0; i < constraints.size(); i++) + case PrepareMeal: + if (job->mat_type != -1) { - ItemConstraint *ct = constraints[i]; + std::vector &food = df::itemdef_foodst::get_vector(); + for (unsigned i = 0; i < food.size(); i++) + if (food[i]->level == job->mat_type) + link_job_constraint(pj, item_type::FOOD, i, 0, -1, -1); + return; + } + break; - if (ct->item.type != itype || - (ct->item.subtype != -1 && ct->item.subtype != isubtype)) - continue; - if (ct->material.isValid() && ct->material != mat) - continue; - if (ct->mat_mask.whole) - { - if (mat.isValid() && !mat.matches(ct->mat_mask)) - continue; - else if (!(job->material_category.whole & ct->mat_mask.whole)) - continue; - } +#define PLANT_PROCESS_MAT(flag, tag) \ + if (!mat.isValid() && !job->job_items.empty()\ + && job->job_items[0]->item_type == item_type::PLANT) \ + mat.decode(job->job_items[0]); \ + if (mat.plant && mat.plant->flags.is_set(plant_raw_flags::flag)) \ + mat.decode(mat.plant->material_defs.type_##tag, \ + mat.plant->material_defs.idx_##tag); \ + else mat.decode(-1); + case MillPlants: + PLANT_PROCESS_MAT(MILL, mill); + break; + case ProcessPlants: + PLANT_PROCESS_MAT(THREAD, thread); + break; + case ProcessPlantsBag: + PLANT_PROCESS_MAT(LEAVES, leaves); + break; + case ProcessPlantsBarrel: + PLANT_PROCESS_MAT(EXTRACT_BARREL, extract_barrel); + break; + case ProcessPlantsVial: + PLANT_PROCESS_MAT(EXTRACT_VIAL, extract_vial); + break; + case ExtractFromPlants: + PLANT_PROCESS_MAT(EXTRACT_STILL_VIAL, extract_still_vial); + break; +#undef PLANT_PROCESS_MAT - ct->jobs.push_back(it->second); - it->second->constraints.push_back(ct); - } + default: + break; + } + + link_job_constraint(pj, itype, isubtype, mat_mask, mat.type, mat.index); +} + +static void map_job_constraints(Core *c) +{ + for (unsigned i = 0; i < constraints.size(); i++) + constraints[i]->jobs.clear(); + + for (TKnownJobs::const_iterator it = known_jobs.begin(); it != known_jobs.end(); ++it) + { + it->second->constraints.clear(); + + if (!it->second->live) + continue; + + compute_job_outputs(c, it->second); } } @@ -669,6 +839,20 @@ static bool itemNotEmpty(df::item *item) return false; } +static bool itemInRealJob(df::item *item) +{ + if (!item->flags.bits.in_job) + return false; + + if (item->jobs.size() != 1 || + item->jobs[0]->unk1 != 2 || + item->jobs[0]->job == NULL) + return true; + + return ENUM_ATTR(job_type, type, item->jobs[0]->job->job_type) + != job_type_class::Hauling; +} + static void map_job_items(Core *c) { for (unsigned i = 0; i < constraints.size(); i++) @@ -685,7 +869,7 @@ static void map_job_items(Core *c) #define F(x) bad_flags.bits.x = true; F(dump); F(forbid); F(garbage_colect); F(hostile); F(on_fire); F(rotten); F(trader); - F(in_building); F(in_job); + F(in_building); F(artifact1); #undef F std::vector &items = df::item::get_vector(); @@ -695,8 +879,8 @@ static void map_job_items(Core *c) if (item->flags.whole & bad_flags.whole) continue; - - bool in_use = item->isAssignedToStockpile() || itemNotEmpty(item); + if (itemInRealJob(item)) + continue; df::item_type itype = item->getType(); int16_t isubtype = item->getSubtype(); @@ -720,16 +904,20 @@ static void map_job_items(Core *c) else { MaterialInfo mat(imattype, imatindex); - bool ok = (!cv->material.isValid() || mat == cv->material) && - (cv->mat_mask.whole == 0 || (mat.isValid() && mat.matches(cv->mat_mask))); + ok = mat.matches(cv->material) && + (cv->mat_mask.whole == 0 || mat.matches(cv->mat_mask)); cv->material_cache[matkey] = ok; } if (!ok) continue; - if (in_use) + if (item->flags.bits.owned || + item->isAssignedToStockpile() || + itemNotEmpty(item)) + { cv->item_inuse++; + } else { cv->item_count++; @@ -763,10 +951,10 @@ static void update_jobs_by_constraints(Core *c) bool goal = pj->actual_job->flags.bits.suspend; - if (suspend_weight >= 0 && suspend_weight >= resume_weight) - goal = true; - else if (resume_weight >= 0) + if (resume_weight >= 0 && resume_weight >= suspend_weight) goal = false; + else if (suspend_weight >= 0 && suspend_weight >= resume_weight) + goal = true; if (goal != pj->actual_job->flags.bits.suspend) { @@ -790,6 +978,60 @@ static void process_constraints(Core *c) /*******************************/ +static void print_constraint(Core *c, ItemConstraint *cv, bool no_job = false, std::string prefix = "") +{ + c->con << prefix << "Constraint " << cv->config.val() << ": " + << (cv->goalByCount() ? "count " : "amount ") + << cv->goalCount() << " (gap " << cv->goalGap() << ")" << endl; + + if (cv->item_count || cv->item_inuse) + c->con << prefix << " items: amount " << cv->item_amount << "; " + << cv->item_count << " stacks available, " + << cv->item_inuse << " in use." << endl; + + if (no_job) return; + + if (cv->jobs.empty()) + c->con.printerr(" (no jobs)\n"); + + for (int i = 0; i < cv->jobs.size(); i++) + { + ProtectedJob *pj = cv->jobs[i]; + df::job *job = pj->actual_job; + + c->con << prefix << " job " << job->id << ": "; + + if (job->job_type != job_type::CustomReaction) + c->con << ENUM_KEY_STR(job_type, job->job_type); + else + c->con << job->reaction_name; + + MaterialInfo mat; + df::dfhack_material_category mat_mask; + guess_job_material(job, mat, mat_mask); + + if (mat.isValid()) + c->con << " [" << mat.toString() << "]"; + else if (mat_mask.whole) + c->con << " [" << bitfieldToString(mat_mask) << "]"; + + if (job->flags.bits.suspend) + c->con << " (suspended)"; + c->con << endl; + } +} + +static void print_job(Core *c, ProtectedJob *pj) +{ + if (!pj) + return; + + printJobDetails(c, pj->job_copy); + + for (int i = 0; i < pj->constraints.size(); i++) + print_constraint(c, pj->constraints[i], true, " "); +} + static command_result workflow_cmd(Core *c, vector & parameters) { CoreSuspender suspend(c); @@ -905,6 +1147,8 @@ static command_result workflow_cmd(Core *c, vector & parameters) icv->setGoalCount(limit); if (parameters.size() >= 4) icv->setGoalGap(atoi(parameters[3].c_str())); + else + icv->setGoalGap(-1); map_job_constraints(c); map_job_items(c);