1 #include "ProductionQueue.h"
2 
3 #include "Empire.h"
4 #include "../universe/BuildingType.h"
5 #include "../universe/Condition.h"
6 #include "../universe/ShipHull.h"
7 #include "../universe/ShipPart.h"
8 #include "../universe/ShipDesign.h"
9 #include "../universe/ValueRef.h"
10 #include "../util/AppInterface.h"
11 #include "../util/GameRules.h"
12 #include "../util/ScopedTimer.h"
13 #include "../util/i18n.h"
14 
15 #include <boost/range/numeric.hpp>
16 #include <boost/range/adaptor/map.hpp>
17 #include <boost/uuid/uuid_io.hpp>
18 
19 
20 namespace {
21     const float EPSILON = 0.001f;
22 
AddRules(GameRules & rules)23     void AddRules(GameRules& rules) {
24         // limits amount of PP per turn that can be imported into the stockpile
25         rules.Add<bool>("RULE_STOCKPILE_IMPORT_LIMITED",
26                         "RULE_STOCKPILE_IMPORT_LIMITED_DESC",
27                         "", false, true);
28 
29         rules.Add<double>("RULE_PRODUCTION_QUEUE_FRONTLOAD_FACTOR",
30                           "RULE_PRODUCTION_QUEUE_FRONTLOAD_FACTOR_DESC",
31                           "", 0.0, true, RangedValidator<double>(0.0, 30.0));
32         rules.Add<double>("RULE_PRODUCTION_QUEUE_TOPPING_UP_FACTOR",
33                           "RULE_PRODUCTION_QUEUE_TOPPING_UP_FACTOR_DESC",
34                           "", 0.0, true, RangedValidator<double>(0.0, 30.0));
35     }
36     bool temp_bool = RegisterGameRules(&AddRules);
37 
38     // Calculates per-turn limit on PP contribution, taking into account unit
39     // item cost, min build turns, blocksize, remaining repeat count, current
40     // progress, and other potential factors discussed below.
41 
42     // RULE_PRODUCTION_QUEUE_FRONTLOAD_FACTOR and
43     // RULE_PRODUCTION_QUEUE_TOPPING_UP_FACTOR specify how the ProductionQueue
44     // will limit allocation towards building a given item on a given turn.
45     // The base amount of maximum allocation per turn (if the player has enough
46     // PP available) is the item's total cost, divided over its minimum build
47     // time.  Sometimes complications arise, though, which unexpectedly delay
48     // the completion even if the item had been fully-funded every turn,
49     // because costs have risen partway through (such as due to increasing ship
50     // costs resulting from recent ship constructoin completion and ensuing
51     // increase of Fleet Maintenance costs.
52     // These two settings provide a mechanism for some allocation leeway to deal
53     // with mid-build cost increases without causing the project  completion to
54     // take an extra turn because of the small bit of increased cost.  The
55     // settings differ in the timing of the extra allocation allowed.
56     // Both factors have a minimum value of 0.0 and a maximum value of 0.3.
57 
58     // Making the frontloaded factor greater than zero increases the per-turn
59     // allocation cap by the specified percentage (so it always spreads the
60     // extra allocation across all turns). Making the topping-up option nonzero
61     // allows the final turn allocation cap to be increased by the specified
62     // percentage of the total cost, if needed (and then subject toavailability
63     // of course). They can both be nonzero, although to avoid that introducing
64     // too much interaction complexity into the minimum build time safeguard for
65     // topping-up,  the topping-up percentage will be reduced by the
66     // frontloading setting.
67 
68     // Note that for very small values of the options (less than 5%), when
69     // dealing with very low cost items the effect/protection may be noticeably
70     // less than expected because of interactions with the ProductionQueue
71     // Epsilon value (0.01)
72 
CalculateProductionPerTurnLimit(const ProductionQueue::Element & queue_element,float item_cost,int build_turns)73     float CalculateProductionPerTurnLimit(const ProductionQueue::Element& queue_element,
74                                           float item_cost, int build_turns)
75     {
76         const float frontload_limit_factor = GetGameRules().Get<double>("RULE_PRODUCTION_QUEUE_FRONTLOAD_FACTOR") * 0.01;
77         // any allowed topping up is limited by how much frontloading was allowed
78         const float topping_up_limit_factor =
79             std::max(0.0, GetGameRules().Get<double>("RULE_PRODUCTION_QUEUE_TOPPING_UP_FACTOR") * 0.01 - frontload_limit_factor);
80 
81         item_cost *= queue_element.blocksize;
82         build_turns = std::max(build_turns, 1);
83         float element_accumulated_PP = queue_element.progress*item_cost;            // effective PP accumulated by this element towards producing next item. progress is a fraction between 0 and 1.
84         float element_total_cost = item_cost * queue_element.remaining;             // total PP to produce all items in this element
85         float additional_pp_to_complete_element =
86             element_total_cost - element_accumulated_PP;                            // additional PP, beyond already-accumulated PP, to produce all items in this element
87         float additional_pp_to_complete_item = item_cost - element_accumulated_PP;  // additional PP, beyond already-accumulated PP, to produce the current item of this element
88         float basic_element_per_turn_limit = item_cost / build_turns;
89         // the extra constraints on frontload and topping up amounts ensure that won't let complete in less than build_turns (so long as costs do not decrease)
90         float frontload = (1.0f + frontload_limit_factor/std::max(build_turns-1,1)) *
91             basic_element_per_turn_limit - 2.0f * EPSILON;
92         float topping_up_limit = basic_element_per_turn_limit +
93             std::min(topping_up_limit_factor * item_cost, basic_element_per_turn_limit - 2 * EPSILON);
94         float topping_up = (additional_pp_to_complete_item < topping_up_limit) ?
95             additional_pp_to_complete_item : basic_element_per_turn_limit;
96         float retval = std::min(additional_pp_to_complete_element,
97                                 std::max(basic_element_per_turn_limit,
98                                          std::max(frontload, topping_up)));
99         //DebugLogger() << "CalculateProductionPerTurnLimit for item " << queue_element.item.build_type << " " << queue_element.item.name
100         //              << " " << queue_element.item.design_id << " :  accumPP: " << element_accumulated_PP << " pp_to_complete_elem: "
101         //              << additional_pp_to_complete_element << " pp_to_complete_item: " << additional_pp_to_complete_item
102         //              <<  " basic_element_per_turn_limit: " << basic_element_per_turn_limit << " frontload: " << frontload
103         //              << " topping_up_limit: " << topping_up_limit << " topping_up: " << topping_up << " retval: " << retval;
104 
105         return retval;
106     }
107 
CalculateNewStockpile(int empire_id,float starting_stockpile,float project_transfer_to_stockpile,const std::map<std::set<int>,float> & available_pp,const std::map<std::set<int>,float> & allocated_pp,const std::map<std::set<int>,float> & allocated_stockpile_pp)108     float CalculateNewStockpile(int empire_id, float starting_stockpile, float project_transfer_to_stockpile,
109                                 const std::map<std::set<int>, float>& available_pp,
110                                 const std::map<std::set<int>, float>& allocated_pp,
111                                 const std::map<std::set<int>, float>& allocated_stockpile_pp)
112     {
113         TraceLogger() << "CalculateNewStockpile for empire " << empire_id;
114         const Empire* empire = GetEmpire(empire_id);
115         if (!empire) {
116             ErrorLogger() << "CalculateStockpileContribution() passed null empire.  doing nothing.";
117             return 0.0f;
118         }
119         float stockpile_limit = empire->GetProductionQueue().StockpileCapacity();
120         float stockpile_used = boost::accumulate(allocated_stockpile_pp | boost::adaptors::map_values, 0.0f);
121         TraceLogger() << " ... stockpile limit: " << stockpile_limit << "  used: " << stockpile_used << "   starting: " << starting_stockpile;
122         float new_contributions = 0.0f;
123         for (auto const& available_group : available_pp) {
124             auto alloc_it = allocated_pp.find(available_group.first);
125             float allocated_here = (alloc_it == allocated_pp.end())
126                 ? 0.0f : alloc_it->second;
127             float excess_here = available_group.second - allocated_here;
128             if (excess_here < EPSILON)
129                 continue;
130             // Transfer excess to stockpile
131             new_contributions += excess_here;
132             TraceLogger() << "...allocated in group: " << allocated_here
133                           << "  excess in group: " << excess_here
134                           << "  to stockpile: " << new_contributions;
135         }
136 
137         if ((new_contributions + project_transfer_to_stockpile) > stockpile_limit &&
138             GetGameRules().Get<bool>("RULE_STOCKPILE_IMPORT_LIMITED"))
139         { new_contributions = stockpile_limit - project_transfer_to_stockpile; }
140 
141         return starting_stockpile + new_contributions + project_transfer_to_stockpile - stockpile_used;
142     }
143 
144     /** Sets the allocated_pp value for each Element in the passed
145       * ProductionQueue \a queue.  Elements are allocated PP based on their need,
146       * the limits they can be given per turn, and the amount available at their
147       * production location (which is itself limited by the resource supply
148       * system groups that are able to exchange resources with the build
149       * location and the amount of minerals and industry produced in the group).
150       * Elements will not receive funding if they cannot be produced by the
151       * empire with the indicated \a empire_id this turn at their build location.
152       * Also checks if elements will be completed this turn.
153       * Returns the amount of PP which gets transferred to the stockpile using
154       * stockpile project build items. */
SetProdQueueElementSpending(std::map<std::set<int>,float> available_pp,float available_stockpile,float stockpile_limit,const std::vector<std::set<int>> & queue_element_resource_sharing_object_groups,const std::map<std::pair<ProductionQueue::ProductionItem,int>,std::pair<float,int>> & queue_item_costs_and_times,const std::vector<bool> & is_producible,ProductionQueue::QueueType & queue,std::map<std::set<int>,float> & allocated_pp,std::map<std::set<int>,float> & allocated_stockpile_pp,int & projects_in_progress,bool simulating)155     float SetProdQueueElementSpending(
156         std::map<std::set<int>, float> available_pp, float available_stockpile,
157         float stockpile_limit,
158         const std::vector<std::set<int>>& queue_element_resource_sharing_object_groups,
159         const std::map<std::pair<ProductionQueue::ProductionItem, int>,
160                        std::pair<float, int>>& queue_item_costs_and_times,
161         const std::vector<bool>& is_producible,
162         ProductionQueue::QueueType& queue,
163         std::map<std::set<int>, float>& allocated_pp,
164         std::map<std::set<int>, float>& allocated_stockpile_pp,
165         int& projects_in_progress, bool simulating)
166     {
167         //DebugLogger() << "========SetProdQueueElementSpending========";
168         //DebugLogger() << "production status: ";
169         //DebugLogger() << "queue: ";
170         //for (const ProductionQueue::Element& elem : queue)
171         //    DebugLogger() << " ... name: " << elem.item.name << "id: " << elem.item.design_id << " allocated: " << elem.allocated_pp << " locationid: " << elem.location << " ordered: " << elem.ordered;
172 
173         if (queue.size() != queue_element_resource_sharing_object_groups.size()) {
174             ErrorLogger() << "SetProdQueueElementSpending queue size and sharing groups size inconsistent. aborting";
175             return 0.0f;
176         }
177 
178         // See explanation at CalculateProductionPerTurnLimit() above regarding operation of these factors.
179         // any allowed topping up is limited by how much frontloading was allowed
180         //const float frontload_limit_factor = GetGameRules().Get<double>("RULE_PRODUCTION_QUEUE_FRONTLOAD_FACTOR") * 0.01;
181         //const float topping_up_limit_factor = std::max(0.0, GetGameRules().Get<double>("RULE_PRODUCTION_QUEUE_TOPPING_UP_FACTOR") * 0.01f - frontload_limit_factor);
182         // DebugLogger() << "SetProdQueueElementSpending frontload  factor " << frontload_limit_factor;
183         // DebugLogger() << "SetProdQueueElementSpending topping up factor " << topping_up_limit_factor;
184 
185         projects_in_progress = 0;
186         allocated_pp.clear();
187         allocated_stockpile_pp.clear();
188         float dummy_pp_source = 0.0f;
189         float stockpile_transfer = 0.0f;
190         //DebugLogger() << "queue size: " << queue.size();
191         int i = 0;
192         for (auto& queue_element : queue) {
193             queue_element.allocated_pp = 0.0f;  // default, to be updated below...
194             if (queue_element.paused) {
195                 TraceLogger() << "allocation: " << queue_element.allocated_pp
196                               << "  to: " << queue_element.item.name
197                               << "  due to it being paused";
198                 ++i;
199                 continue;
200             }
201 
202             // get resource sharing group and amount of resource available to build this item
203             const auto& group = queue_element_resource_sharing_object_groups[i];
204             auto available_pp_it = available_pp.find(group);
205             float& group_pp_available = (available_pp_it != available_pp.end()) ?
206                                         available_pp_it->second : dummy_pp_source;
207 
208             if ((group_pp_available <= 0) &&
209                 (available_stockpile <= 0 || !queue_element.allowed_imperial_stockpile_use))
210             {
211                 TraceLogger() << "allocation: " << queue_element.allocated_pp
212                               << "  to: " << queue_element.item.name
213                               << "  due to lack of available PP in group";
214                 queue_element.allocated_pp = 0.0f;
215                 ++i;
216                 continue;
217             }
218 
219             //DebugLogger() << "group has " << group_pp_available << " PP available";
220 
221             // see if item is producible this turn...
222             if (!is_producible[i]) {
223                 // can't be produced at this location this turn.
224                 queue_element.allocated_pp = 0.0f;
225                 TraceLogger() << "allocation: " << queue_element.allocated_pp
226                               << "  to unproducible item: " << queue_element.item.name;
227                 ++i;
228                 continue;
229             }
230 
231             // get max contribution per turn and turns to build at max contribution rate
232             int location_id = (queue_element.item.CostIsProductionLocationInvariant() ?
233                 INVALID_OBJECT_ID : queue_element.location);
234             std::pair<ProductionQueue::ProductionItem, int> key(queue_element.item, location_id);
235             float item_cost = 1e6;  // dummy/default value, shouldn't ever really be needed
236             int build_turns = 1;    // dummy/default value, shouldn't ever really be needed
237             auto time_cost_it = queue_item_costs_and_times.find(key);
238             if (time_cost_it != queue_item_costs_and_times.end()) {
239                 item_cost = time_cost_it->second.first;
240                 build_turns = time_cost_it->second.second;
241             } else {
242                 ErrorLogger() << "item: " << queue_element.item.name
243                               << "  somehow failed time cost lookup for location " << location_id;
244             }
245             //DebugLogger() << "item " << queue_element.item.name << " costs " << item_cost << " for " << build_turns << " turns";
246 
247             float element_this_turn_limit = CalculateProductionPerTurnLimit(queue_element, item_cost, build_turns);
248 
249             // determine how many pp to allocate to this queue element block this turn.  allocation is limited by the
250             // item cost, which is the max number of PP per turn that can be put towards this item, and by the
251             // total cost remaining to complete the last item in the queue element (eg. the element has all but
252             // the last item complete already) and by the total pp available in this element's production location's
253             // resource sharing group (including any stockpile availability)
254             float stockpile_available_for_this =
255                 (queue_element.allowed_imperial_stockpile_use) ? available_stockpile : 0;
256 
257             float allocation = std::max(0.0f,
258                 std::min(element_this_turn_limit,
259                          group_pp_available + stockpile_available_for_this));
260 
261             if (queue_element.item.build_type == BT_STOCKPILE) {
262                 if (GetGameRules().Get<bool>("RULE_STOCKPILE_IMPORT_LIMITED")) {
263                     float unused_limit = std::max(0.0f, stockpile_limit - stockpile_transfer);
264                     allocation = std::min(allocation, unused_limit);
265                 }
266             }
267 
268             //DebugLogger() << "element accumulated " << element_accumulated_PP << " of total cost "
269             //                       << element_total_cost << " and needs " << additional_pp_to_complete_element
270             //                       << " more to be completed";
271             //DebugLogger() << "... allocating " << allocation;
272 
273             // allocate pp
274             queue_element.allocated_pp = std::max(allocation, EPSILON);
275 
276             // record allocation from group
277             float group_drawdown = std::min(allocation, group_pp_available);
278 
279             allocated_pp[group] += group_drawdown;  // relies on default initial mapped value of 0.0f
280             if (queue_element.item.build_type == BT_STOCKPILE) {
281                 stockpile_transfer += group_drawdown;
282             }
283             group_pp_available -= group_drawdown;
284 
285             float stockpile_drawdown = allocation <= group_drawdown ? 0.0f : (allocation - group_drawdown);
286             TraceLogger() << "allocation: " << allocation
287                           << "  to: " << queue_element.item.name
288                           << "  from group: " << group_drawdown
289                           << "  from stockpile: " << stockpile_drawdown
290                           << "  to stockpile:" << stockpile_transfer
291                           << "  group remaining: " << group_pp_available;
292 
293             // record allocation from stockpile
294             // protect against any slight mismatch that might possible happen from multiplying
295             // and dividing by a very very small stockpile_conversion_rate
296             stockpile_drawdown = std::min(stockpile_drawdown, available_stockpile);
297             if (stockpile_drawdown > 0) {
298                 allocated_stockpile_pp[group] += stockpile_drawdown;
299                 available_stockpile -= stockpile_drawdown;
300             }
301 
302             // check for completion
303             float block_cost = item_cost * queue_element.blocksize;
304             if (block_cost*(1.0f - queue_element.progress) - queue_element.allocated_pp < EPSILON)
305                 queue_element.turns_left_to_next_item = 1;
306 
307             // if simulating, update progress
308             if (simulating)
309                 queue_element.progress += allocation / std::max(EPSILON, block_cost);    // add turn's progress due to allocation
310 
311             if (allocation > 0.0f)
312                 ++projects_in_progress;
313 
314             ++i;
315         }
316         return stockpile_transfer;
317     }
318 }
319 
320 
ProductionItem()321 ProductionQueue::ProductionItem::ProductionItem()
322 {}
323 
ProductionItem(BuildType build_type_)324 ProductionQueue::ProductionItem::ProductionItem(BuildType build_type_) :
325     build_type(build_type_)
326 {
327     if (build_type_ == BT_STOCKPILE)
328         name = UserStringNop("PROJECT_BT_STOCKPILE");
329 }
330 
ProductionItem(BuildType build_type_,std::string name_)331 ProductionQueue::ProductionItem::ProductionItem(BuildType build_type_, std::string name_) :
332     build_type(build_type_),
333     name(name_)
334 {}
335 
ProductionItem(BuildType build_type_,int design_id_)336 ProductionQueue::ProductionItem::ProductionItem(BuildType build_type_, int design_id_) :
337     build_type(build_type_),
338     design_id(design_id_)
339 {
340     if (build_type == BT_SHIP) {
341         if (const ShipDesign* ship_design = GetShipDesign(design_id))
342             name = ship_design->Name();
343         else
344             ErrorLogger() << "ProductionItem::ProductionItem couldn't get ship design with id: " << design_id;
345     }
346 }
347 
CostIsProductionLocationInvariant() const348 bool ProductionQueue::ProductionItem::CostIsProductionLocationInvariant() const {
349     if (build_type == BT_BUILDING) {
350         const BuildingType* type = GetBuildingType(name);
351         if (!type)
352             return true;
353         return type->ProductionCostTimeLocationInvariant();
354 
355     } else if (build_type == BT_SHIP) {
356         const ShipDesign* design = GetShipDesign(design_id);
357         if (!design)
358             return true;
359         return design->ProductionCostTimeLocationInvariant();
360 
361     } else if (build_type == BT_STOCKPILE) {
362         return true;
363     }
364     return false;
365 }
366 
EnqueueConditionPassedAt(int location_id) const367 bool ProductionQueue::ProductionItem::EnqueueConditionPassedAt(int location_id) const {
368     switch (build_type) {
369     case BT_BUILDING: {
370         if (const BuildingType* bt = GetBuildingType(name)) {
371             auto location_obj = Objects().get(location_id);
372             auto c = bt->EnqueueLocation();
373             if (!c)
374                 return true;
375             return c->Eval(ScriptingContext(location_obj), location_obj);
376         }
377         return true;
378         break;
379     }
380     case BT_SHIP:   // ships don't have enqueue location conditions
381     case BT_STOCKPILE:  // stockpile can always be enqueued
382     default:
383         return true;
384     }
385 }
386 
operator <(const ProductionItem & rhs) const387 bool ProductionQueue::ProductionItem::operator<(const ProductionItem& rhs) const {
388     if (build_type < rhs.build_type)
389         return true;
390     if (build_type > rhs.build_type)
391         return false;
392     if (build_type == BT_BUILDING)
393         return name < rhs.name;
394     else if (build_type == BT_SHIP)
395         return design_id < rhs.design_id;
396 
397     return false;
398 }
399 
400 std::map<std::string, std::map<int, float>>
CompletionSpecialConsumption(int location_id) const401 ProductionQueue::ProductionItem::CompletionSpecialConsumption(int location_id) const {
402     std::map<std::string, std::map<int, float>> retval;
403 
404     switch (build_type) {
405     case BT_BUILDING: {
406         if (const BuildingType* bt = GetBuildingType(name)) {
407             auto location_obj = Objects().get(location_id);
408             ScriptingContext context(location_obj);
409 
410             for (const auto& psc : bt->ProductionSpecialConsumption()) {
411                 if (!psc.second.first)
412                     continue;
413                 Condition::ObjectSet matches;
414                 // if a condition selectin gwhere to take resources from was specified, use it.
415                 // Otherwise take from the production location
416                 if (psc.second.second) {
417                     psc.second.second->Eval(context, matches);
418                 } else {
419                     matches.push_back(location_obj);
420                 }
421 
422                 // determine how much to take from each matched object
423                 for (auto& object : matches) {
424                     context.effect_target = std::const_pointer_cast<UniverseObject>(object);
425                     retval[psc.first][object->ID()] += psc.second.first->Eval(context);
426                 }
427             }
428         }
429         break;
430     }
431     case BT_SHIP: {
432         if (const ShipDesign* sd = GetShipDesign(design_id)) {
433             auto location_obj = Objects().get(location_id);
434             ScriptingContext context(location_obj);
435 
436             if (const ShipHull* ship_hull = GetShipHull(sd->Hull())) {
437                 for (const auto& psc : ship_hull->ProductionSpecialConsumption()) {
438                     if (!psc.second.first)
439                         continue;
440                     retval[psc.first][location_id] += psc.second.first->Eval(context);
441                 }
442             }
443 
444             for (const std::string& part_name : sd->Parts()) {
445                 const ShipPart* part = GetShipPart(part_name);
446                 if (!part)
447                     continue;
448                 for (const auto& psc : part->ProductionSpecialConsumption()) {
449                     if (!psc.second.first)
450                         continue;
451                     retval[psc.first][location_id] += psc.second.first->Eval(context);
452                 }
453             }
454         }
455         break;
456     }
457     case BT_PROJECT:    // TODO
458     case BT_STOCKPILE:  // stockpile transfer consumes no special
459     default:
460         break;
461     }
462 
463     return retval;
464 }
465 
466 std::map<MeterType, std::map<int, float>>
CompletionMeterConsumption(int location_id) const467 ProductionQueue::ProductionItem::CompletionMeterConsumption(int location_id) const {
468     std::map<MeterType, std::map<int, float>> retval;
469 
470     switch (build_type) {
471     case BT_BUILDING: {
472         if (const BuildingType* bt = GetBuildingType(name)) {
473             auto obj = Objects().get(location_id);
474             ScriptingContext context(obj);
475 
476             for (const auto& pmc : bt->ProductionMeterConsumption()) {
477                 if (!pmc.second.first)
478                     continue;
479                 retval[pmc.first][location_id] = pmc.second.first->Eval(context);
480             }
481         }
482         break;
483     }
484     case BT_SHIP: {
485         if (const ShipDesign* sd = GetShipDesign(design_id)) {
486             auto obj = Objects().get(location_id);
487             ScriptingContext context(obj);
488 
489             if (const ShipHull* ship_hull = GetShipHull(sd->Hull())) {
490                 for (const auto& pmc : ship_hull->ProductionMeterConsumption()) {
491                     if (!pmc.second.first)
492                         continue;
493                     retval[pmc.first][location_id] += pmc.second.first->Eval(context);
494                 }
495             }
496 
497             for (const std::string& part_name : sd->Parts()) {
498                 const ShipPart* pt = GetShipPart(part_name);
499                 if (!pt)
500                     continue;
501                 for (const auto& pmc : pt->ProductionMeterConsumption()) {
502                     if (!pmc.second.first)
503                         continue;
504                     retval[pmc.first][location_id] += pmc.second.first->Eval(context);
505                 }
506             }
507         }
508         break;
509     }
510     case BT_PROJECT:    // TODO
511     case BT_STOCKPILE:  // stockpile transfer happens before completion - nothing to do
512     default:
513         break;
514     }
515 
516     return retval;
517 }
518 
Dump() const519 std::string ProductionQueue::ProductionItem::Dump() const {
520     std::string retval = "ProductionItem: " + boost::lexical_cast<std::string>(build_type);
521     if (!name.empty())
522         retval += " name: " + name;
523     if (design_id != INVALID_DESIGN_ID)
524         retval += " id: " + std::to_string(design_id);
525     return retval;
526 }
527 
528 
529 //////////////////////////////
530 // ProductionQueue::Element //
531 //////////////////////////////
Element()532 ProductionQueue::Element::Element() :
533      uuid(boost::uuids::nil_generator()())
534 {}
535 
Element(ProductionItem item_,int empire_id_,boost::uuids::uuid uuid_,int ordered_,int remaining_,int blocksize_,int location_,bool paused_,bool allowed_imperial_stockpile_use_)536 ProductionQueue::Element::Element(ProductionItem item_, int empire_id_,
537                                   boost::uuids::uuid uuid_, int ordered_,
538                                   int remaining_, int blocksize_, int location_, bool paused_,
539                                   bool allowed_imperial_stockpile_use_) :
540     item(item_),
541     empire_id(empire_id_),
542     ordered(ordered_),
543     blocksize(blocksize_),
544     remaining(remaining_),
545     location(location_),
546     blocksize_memory(blocksize_),
547     paused(paused_),
548     allowed_imperial_stockpile_use(allowed_imperial_stockpile_use_),
549     uuid(uuid_)
550 {}
551 
Element(BuildType build_type,std::string name,int empire_id_,boost::uuids::uuid uuid_,int ordered_,int remaining_,int blocksize_,int location_,bool paused_,bool allowed_imperial_stockpile_use_)552 ProductionQueue::Element::Element(BuildType build_type, std::string name, int empire_id_,
553                                   boost::uuids::uuid uuid_, int ordered_,
554                                   int remaining_, int blocksize_, int location_, bool paused_,
555                                   bool allowed_imperial_stockpile_use_) :
556     item(build_type, name),
557     empire_id(empire_id_),
558     ordered(ordered_),
559     blocksize(blocksize_),
560     remaining(remaining_),
561     location(location_),
562     blocksize_memory(blocksize_),
563     paused(paused_),
564     allowed_imperial_stockpile_use(allowed_imperial_stockpile_use_),
565     uuid(uuid_)
566 {}
567 
Element(BuildType build_type,int design_id,int empire_id_,boost::uuids::uuid uuid_,int ordered_,int remaining_,int blocksize_,int location_,bool paused_,bool allowed_imperial_stockpile_use_)568 ProductionQueue::Element::Element(BuildType build_type, int design_id, int empire_id_,
569                                   boost::uuids::uuid uuid_, int ordered_,
570                                   int remaining_, int blocksize_, int location_, bool paused_,
571                                   bool allowed_imperial_stockpile_use_) :
572     item(build_type, design_id),
573     empire_id(empire_id_),
574     ordered(ordered_),
575     blocksize(blocksize_),
576     remaining(remaining_),
577     location(location_),
578     blocksize_memory(blocksize_),
579     paused(paused_),
580     allowed_imperial_stockpile_use(allowed_imperial_stockpile_use_),
581     uuid(uuid_)
582 {}
583 
Dump() const584 std::string ProductionQueue::Element::Dump() const {
585     std::string retval = "ProductionQueue::Element (" + item.Dump() + ") (" +
586         std::to_string(blocksize) + ") x" + std::to_string(ordered) + " ";
587     retval += " (remaining: " + std::to_string(remaining) + ")  uuid: " + boost::uuids::to_string(uuid);
588     return retval;
589 }
590 
591 
592 /////////////////////
593 // ProductionQueue //
594 /////////////////////
ProductionQueue(int empire_id)595 ProductionQueue::ProductionQueue(int empire_id) :
596     m_empire_id(empire_id)
597 {}
598 
ProjectsInProgress() const599 int ProductionQueue::ProjectsInProgress() const
600 { return m_projects_in_progress; }
601 
TotalPPsSpent() const602 float ProductionQueue::TotalPPsSpent() const {
603     // add up allocated PP from all resource sharing object groups
604     float retval = 0.0f;
605     for (const auto& entry : m_object_group_allocated_pp)
606     { retval += entry.second; }
607     return retval;
608 }
609 
610 /** TODO: Is there any reason to keep this method in addition to the more
611   * specific information directly available from the empire?  This should
612   * probably at least be renamed to clarify it is non-stockpile output */
AvailablePP(const std::shared_ptr<ResourcePool> & industry_pool) const613 std::map<std::set<int>, float> ProductionQueue::AvailablePP(
614     const std::shared_ptr<ResourcePool>& industry_pool) const
615 {
616     std::map<std::set<int>, float> retval;
617     if (!industry_pool) {
618         ErrorLogger() << "ProductionQueue::AvailablePP passed invalid industry resource pool";
619         return retval;
620     }
621 
622     // determine available PP (ie. industry) in each resource sharing group of systems
623     for (const auto& ind : industry_pool->Output()) {   // get group of systems in industry pool
624         const std::set<int>& group = ind.first;
625         retval[group] = ind.second;
626     }
627 
628     return retval;
629 }
630 
AllocatedPP() const631 const std::map<std::set<int>, float>& ProductionQueue::AllocatedPP() const
632 { return m_object_group_allocated_pp; }
633 
AllocatedStockpilePP() const634 const std::map<std::set<int>, float>& ProductionQueue::AllocatedStockpilePP() const
635 { return m_object_group_allocated_stockpile_pp; }
636 
StockpileCapacity() const637 float ProductionQueue::StockpileCapacity() const {
638     if (m_empire_id == ALL_EMPIRES)
639         return 0.0f;
640 
641     float retval = 0.0f;
642 
643     for (const auto& obj : Objects().ExistingObjects()) {
644         if (!obj.second->OwnedBy(m_empire_id))
645             continue;
646         const auto* meter = obj.second->GetMeter(METER_STOCKPILE);
647         if (!meter)
648             continue;
649         retval += meter->Current();
650     }
651     return retval;
652 }
653 
ObjectsWithWastedPP(const std::shared_ptr<ResourcePool> & industry_pool) const654 std::set<std::set<int>> ProductionQueue::ObjectsWithWastedPP(
655     const std::shared_ptr<ResourcePool>& industry_pool) const
656 {
657     std::set<std::set<int>> retval;
658     if (!industry_pool) {
659         ErrorLogger() << "ProductionQueue::ObjectsWithWastedPP passed invalid industry resource pool";
660         return retval;
661     }
662 
663     for (auto& avail_pp : AvailablePP(industry_pool)) {
664         //std::cout << "available PP groups size: " << avail_pp.first.size() << " pp: " << avail_pp.second << std::endl;
665 
666         if (avail_pp.second <= 0)
667             continue;   // can't waste if group has no PP
668         const std::set<int>& group = avail_pp.first;
669         // find this group's allocated PP
670         auto alloc_it = m_object_group_allocated_pp.find(group);
671         // is less allocated than is available?  if so, some is wasted (assumes stockpile contribuutions can never be lossless)
672         // XXX maybe should check stockpile input ratio
673         if (alloc_it == m_object_group_allocated_pp.end() || alloc_it->second < avail_pp.second)
674             retval.insert(avail_pp.first);
675     }
676     return retval;
677 }
678 
empty() const679 bool ProductionQueue::empty() const
680 { return !m_queue.size(); }
681 
size() const682 unsigned int ProductionQueue::size() const
683 { return m_queue.size(); }
684 
begin() const685 ProductionQueue::const_iterator ProductionQueue::begin() const
686 { return m_queue.begin(); }
687 
end() const688 ProductionQueue::const_iterator ProductionQueue::end() const
689 { return m_queue.end(); }
690 
find(int i) const691 ProductionQueue::const_iterator ProductionQueue::find(int i) const
692 { return (0 <= i && i < static_cast<int>(size())) ? (begin() + i) : end(); }
693 
operator [](int i) const694 const ProductionQueue::Element& ProductionQueue::operator[](int i) const {
695     if (i < 0 || i >= static_cast<int>(m_queue.size()))
696         throw std::out_of_range("Tried to access ProductionQueue element out of bounds");
697     return m_queue[i];
698 }
699 
find(boost::uuids::uuid uuid) const700 ProductionQueue::const_iterator ProductionQueue::find(boost::uuids::uuid uuid) const {
701     if (uuid == boost::uuids::nil_generator()())
702         return m_queue.end();
703     for (auto it = m_queue.begin(); it != m_queue.end(); ++it)
704         if (it->uuid == uuid)
705             return it;
706     return m_queue.end();
707 }
708 
IndexOfUUID(boost::uuids::uuid uuid) const709 int ProductionQueue::IndexOfUUID(boost::uuids::uuid uuid) const {
710     auto it = find(uuid);
711     if (it == end())
712         return -1;
713     return std::distance(begin(), it);
714 }
715 
Update()716 void ProductionQueue::Update() {
717     const Empire* empire = GetEmpire(m_empire_id);
718     if (!empire) {
719         ErrorLogger() << "ProductionQueue::Update passed null empire.  doing nothing.";
720         m_projects_in_progress = 0;
721         m_object_group_allocated_pp.clear();
722         return;
723     }
724 
725     ScopedTimer update_timer("ProductionQueue::Update");
726 
727     auto industry_resource_pool = empire->GetResourcePool(RE_INDUSTRY);
728     auto available_pp = AvailablePP(industry_resource_pool);
729     float pp_in_stockpile = industry_resource_pool->Stockpile();
730     TraceLogger() << "========= pp_in_stockpile:     " << pp_in_stockpile << " ========";
731     float stockpile_limit = StockpileCapacity();
732     float available_stockpile = std::min(pp_in_stockpile, stockpile_limit);
733     TraceLogger() << "========= available_stockpile: " << available_stockpile << " ========";
734 
735     // determine which resource sharing group each queue item is located in
736     std::vector<std::set<int>> queue_element_groups;
737     for (const auto& element : m_queue) {
738         // get location object for element
739         int location_id = element.location;
740 
741         // search through groups to find object
742         for (auto groups_it = available_pp.begin();
743              true; ++groups_it)
744         {
745             if (groups_it == available_pp.end()) {
746                 // didn't find a group containing this object, so add an empty group as this element's queue element group
747                 queue_element_groups.push_back(std::set<int>());
748                 break;
749             }
750 
751             // check if location object id is in this group
752             const auto& group = groups_it->first;
753             auto set_it = group.find(location_id);
754             if (set_it != group.end()) {
755                 // system is in this group.
756                 queue_element_groups.push_back(group);  // record this discovery
757                 break;                                  // stop searching for a group containing a system, since one has been found
758             }
759         }
760     }
761 
762     // cache producibility, and production item costs and times
763     // initialize production queue item completion status to 'never'
764     std::map<std::pair<ProductionQueue::ProductionItem, int>,
765              std::pair<float, int>> queue_item_costs_and_times;
766     std::vector<bool> is_producible;
767     for (auto& elem : m_queue) {
768         is_producible.push_back(empire->ProducibleItem(elem.item, elem.location));
769         // for items that don't depend on location, only store cost/time once
770         int location_id = (elem.item.CostIsProductionLocationInvariant() ? INVALID_OBJECT_ID : elem.location);
771         std::pair<ProductionQueue::ProductionItem, int> key(elem.item, location_id);
772 
773         if (!queue_item_costs_and_times.count(key))
774             queue_item_costs_and_times[key] = empire->ProductionCostAndTime(elem);
775 
776         elem.turns_left_to_next_item = -1;
777         elem.turns_left_to_completion = -1;
778     }
779 
780     // duplicate production queue state for future simulation
781     QueueType sim_queue = m_queue;
782     std::vector<unsigned int> sim_queue_original_indices(sim_queue.size());
783     for (unsigned int i = 0; i < sim_queue_original_indices.size(); ++i)
784         sim_queue_original_indices[i] = i;
785 
786     // allocate pp to queue elements, returning updated available pp and updated
787     // allocated pp for each group of resource sharing objects
788     float project_transfer_to_stockpile = SetProdQueueElementSpending(
789         available_pp, available_stockpile, stockpile_limit, queue_element_groups,
790         queue_item_costs_and_times, is_producible, m_queue,
791         m_object_group_allocated_pp, m_object_group_allocated_stockpile_pp,
792         m_projects_in_progress, false);
793 
794     //update expected new stockpile amount
795     m_expected_new_stockpile_amount = CalculateNewStockpile(
796         m_empire_id, pp_in_stockpile, project_transfer_to_stockpile, available_pp, m_object_group_allocated_pp,
797         m_object_group_allocated_stockpile_pp);
798     m_expected_project_transfer_to_stockpile = project_transfer_to_stockpile;
799 
800     // if at least one resource-sharing system group have available PP, simulate
801     // future turns to predict when build items will be finished
802     bool simulate_future = false;
803     for (auto& available : available_pp) {
804         if (available.second > EPSILON) {
805             simulate_future = true;
806             break;
807         }
808     }
809 
810     if (!simulate_future) {
811         DebugLogger() << "not enough PP to be worth simulating future turns production.  marking everything as never complete";
812         ProductionQueueChangedSignal();
813         return;
814     }
815 
816     // there are enough PP available in at least one group to make it worthwhile to simulate the future.
817     DebugLogger() << "ProductionQueue::Update: Simulating future turns of production queue";
818 
819     const int TOO_MANY_TURNS = 500;     // stop counting turns to completion after this long, to prevent seemingly endless loops
820     const float TOO_LONG_TIME = 0.5f;   // max time in seconds to spend simulating queue
821 
822 
823     // remove from simulated queue any paused items and items that can't be built due to not
824     // meeting their location conditions; can't feasibly re-check
825     // buildability each projected turn as this would require creating a simulated
826     // universe into which simulated completed buildings could be inserted, as
827     // well as spoofing the current turn, or otherwise faking the results for
828     // evaluating arbitrary location conditions for the simulated universe.
829     // this would also be inaccurate anyway due to player choices or random
830     // chance, so for simplicity, it is assumed that building location
831     // conditions evaluated at the present turn apply indefinitely.
832     //
833     for (unsigned int i = 0; i < sim_queue.size(); ++i) {
834         if (sim_queue[i].paused || !is_producible[i]) {
835             sim_queue.erase(sim_queue.begin() + i);
836             is_producible.erase(is_producible.begin() + i);
837             queue_element_groups.erase(queue_element_groups.begin() + i);
838             sim_queue_original_indices.erase(sim_queue_original_indices.begin() + i--);
839         }
840     }
841 
842     boost::posix_time::ptime sim_time_start;
843     boost::posix_time::ptime sim_time_end;
844     long sim_time;
845     sim_time_start = boost::posix_time::ptime(boost::posix_time::microsec_clock::local_time());
846     std::map<std::set<int>, float>  allocated_pp;
847     float sim_available_stockpile = available_stockpile;
848     float sim_pp_in_stockpile = pp_in_stockpile;
849     std::map<std::set<int>, float>  allocated_stockpile_pp;
850     int dummy_int = 0;
851 
852     for (int sim_turn = 1; sim_turn <= TOO_MANY_TURNS; sim_turn ++) {
853         long sim_time_until_now = (boost::posix_time::ptime(boost::posix_time::microsec_clock::local_time()) - sim_time_start).total_microseconds();
854         if ((sim_time_until_now * 1e-6) >= TOO_LONG_TIME)
855             break;
856 
857         TraceLogger() << "sim turn: " << sim_turn << "  sim queue size: " << sim_queue.size();
858         if (sim_queue.empty() && sim_turn > 2)
859             break;
860 
861         allocated_pp.clear();
862         allocated_stockpile_pp.clear();
863 
864         float sim_project_transfer_to_stockpile = SetProdQueueElementSpending(
865             available_pp, sim_available_stockpile, stockpile_limit, queue_element_groups,
866             queue_item_costs_and_times, is_producible, sim_queue,
867             allocated_pp, allocated_stockpile_pp, dummy_int, true);
868 
869         // check completion status and update m_queue and sim_queue as appropriate
870         for (unsigned int i = 0; i < sim_queue.size(); i++) {
871             ProductionQueue::Element& sim_element = sim_queue[i];
872             ProductionQueue::Element& orig_element = m_queue[sim_queue_original_indices[i]];
873             if (sim_element.turns_left_to_next_item != 1)
874                 continue;
875             sim_element.progress = std::max(0.0f, sim_element.progress - 1.0f);
876             if (orig_element.turns_left_to_next_item == -1)
877                 orig_element.turns_left_to_next_item = sim_turn;
878             sim_element.turns_left_to_next_item = -1;
879 
880             // if all repeats of item are complete, update completion time and remove from sim_queue
881             if (--sim_element.remaining == 0) {
882                 orig_element.turns_left_to_completion = sim_turn;
883                 sim_queue.erase(sim_queue.begin() + i);
884                 is_producible.erase(is_producible.begin() + i);
885                 queue_element_groups.erase(queue_element_groups.begin() + i);
886                 sim_queue_original_indices.erase(sim_queue_original_indices.begin() + i--);
887             }
888         }
889         sim_pp_in_stockpile = CalculateNewStockpile(
890             m_empire_id, sim_pp_in_stockpile, sim_project_transfer_to_stockpile,
891             available_pp, allocated_pp, allocated_stockpile_pp);
892         sim_available_stockpile = std::min(sim_pp_in_stockpile, stockpile_limit);
893     }
894 
895     sim_time_end = boost::posix_time::ptime(boost::posix_time::microsec_clock::local_time());
896     sim_time = (sim_time_end - sim_time_start).total_microseconds();
897     if ((sim_time * 1e-6) >= TOO_LONG_TIME) {
898         DebugLogger()  << "ProductionQueue::Update: Projections timed out after " << sim_time
899                        << " microseconds; all remaining items in queue marked completing 'Never'.";
900     }
901     DebugLogger() << "ProductionQueue::Update: Projections took "
902                   << ((sim_time_end - sim_time_start).total_microseconds()) << " microseconds with "
903                   << empire->ResourceOutput(RE_INDUSTRY) << " industry output";
904     ProductionQueueChangedSignal();
905 }
906 
push_back(const Element & element)907 void ProductionQueue::push_back(const Element& element) {
908     if (find(element.uuid) != end()) {
909         ErrorLogger() << "Trying to push back repeated UUID " << element.uuid;
910         throw std::invalid_argument("Repeated use of UUID");
911     }
912     m_queue.push_back(element);
913 }
914 
insert(iterator it,const Element & element)915 void ProductionQueue::insert(iterator it, const Element& element) {
916     if (find(element.uuid) != end()) {
917         ErrorLogger() << "Trying to insert repeated UUID " << element.uuid;
918         throw std::invalid_argument("Repeated use of UUID");
919     }
920     m_queue.insert(it, element);
921 }
922 
erase(int i)923 void ProductionQueue::erase(int i) {
924     if (i < 0 || i >= static_cast<int>(m_queue.size()))
925         throw std::out_of_range("Tried to erase ProductionQueue item out of bounds.");
926     m_queue.erase(begin() + i);
927 }
928 
erase(iterator it)929 ProductionQueue::iterator ProductionQueue::erase(iterator it) {
930     if (it == end())
931         throw std::out_of_range("Tried to erase ProductionQueue item out of bounds.");
932     return m_queue.erase(it);
933 }
934 
begin()935 ProductionQueue::iterator ProductionQueue::begin()
936 { return m_queue.begin(); }
937 
end()938 ProductionQueue::iterator ProductionQueue::end()
939 { return m_queue.end(); }
940 
find(int i)941 ProductionQueue::iterator ProductionQueue::find(int i)
942 { return (0 <= i && i < static_cast<int>(size())) ? (begin() + i) : end(); }
943 
operator [](int i)944 ProductionQueue::Element& ProductionQueue::operator[](int i) {
945     if (i < 0 || i >= static_cast<int>(m_queue.size()))
946         throw std::out_of_range("Tried to access ProductionQueue element out of bounds");
947     return m_queue[i];
948 }
949 
clear()950 void ProductionQueue::clear() {
951     m_queue.clear();
952     m_projects_in_progress = 0;
953     m_object_group_allocated_pp.clear();
954     ProductionQueueChangedSignal();
955 }
956