Index: base.tab =================================================================== --- base.tab (revision 8016) +++ base.tab (working copy) @@ -2925,26 +2925,30 @@ note=halt detail window - obj=program_text -name=Net ID: %u\n +name=Net ID: %lu note=powernet info window - obj=program_text -name=\nCapacity: %u MW\n +name=Capacity: %lu MW note=powernet info window - obj=program_text -name=\nDemand: %u MW\n +name=Demand: %lu MW note=powernet info window - obj=program_text -name=\nGeneration: %u MW\n +name=Generation: %lu MW note=powernet info window - obj=program_text -name=\nAct. Load: %u MW\n +name=Usage: %u %% note=powernet info window - obj=program_text +name=\nAct. Load: %u MW\n +note=powernet info window (depricated) +- +obj=program_text name=Supplied: %u %% note=powernet info window - Index: trunk/dataobj/powernet.cc =================================================================== --- trunk/dataobj/powernet.cc (revision 8016) +++ trunk/dataobj/powernet.cc (working copy) @@ -13,11 +13,14 @@ static pthread_mutex_t netlist_mutex = PTHREAD_MUTEX_INITIALIZER; #endif -// max capacity = (max >> 5) -1, see senke_t::step in obj/leitung2.cc -//uint64 powernet_t::max_capacity = (1<<44)-1; // max to allow display with uint32 after POWER_TO_MW shift -const uint64 powernet_t::max_capacity = (1953125ull<<23); // nicer number for human display (corresponds to 4 TW) +extern const uint32 POWER_TO_MW; // defined in leitung2 +// maximum possible limit is (1 << (63 - FRACTION_PRECISION)) +const uint64 powernet_t::max_capacity = (uint64)4000000 << POWER_TO_MW; // (4 TW) +const uint8 powernet_t::FRACTION_PRECISION = 16; + + slist_tpl powernet_t::powernet_list; @@ -48,10 +51,11 @@ pthread_mutex_unlock( &netlist_mutex ); #endif - this_supply = 0; - next_supply = 0; - this_demand = 0; - next_demand = 0; + power_supply = 0; + power_demand = 0; + + norm_demand = 1 << FRACTION_PRECISION; + norm_supply = 1 << FRACTION_PRECISION; } @@ -66,6 +70,20 @@ #endif } +/** + * Computes a normalized supply/demand value. + * If demand fully satisfies supply or supply is 0 then output is 1. + */ +sint32 compute_norm_sd(uint64 const demand, uint64 const supply) +{ + // compute demand factor + if( (demand >= supply) || (supply == 0) ) { + return (sint32)1 << powernet_t::FRACTION_PRECISION; + } + else { + return (sint32)((demand << powernet_t::FRACTION_PRECISION) / supply); + } +} void powernet_t::step(uint32 delta_t) { @@ -73,26 +91,29 @@ return; } - this_supply = next_supply; - next_supply = 0; - this_demand = next_demand; - next_demand = 0; + // get limited values + uint64 const supply = get_supply(); + uint64 const demand = get_demand(); + + // compute normalized demand + norm_demand = compute_norm_sd(demand, supply); + norm_supply = compute_norm_sd(supply, demand); } +/** + * Clamps a power value to be within the maximum capacity. + */ +uint64 clamp_power(uint64 const power) +{ + return power > powernet_t::max_capacity ? powernet_t::max_capacity : power; +} -void powernet_t::add_supply(const uint32 p) +uint64 powernet_t::get_supply() const { - next_supply += p; - if( next_supply>max_capacity ) { - next_supply = max_capacity; - } + return clamp_power(power_supply); } - -void powernet_t::add_demand(const uint32 p) +uint64 powernet_t::get_demand() const { - next_demand += p; - if( next_demand>max_capacity ) { - next_demand = max_capacity; - } -} + return clamp_power(power_demand); +} \ No newline at end of file Index: trunk/dataobj/powernet.h =================================================================== --- trunk/dataobj/powernet.h (revision 8016) +++ trunk/dataobj/powernet.h (working copy) @@ -13,7 +13,6 @@ #include "../simtypes.h" #include "../tpl/slist_tpl.h" - /** * Data class for power networks. A two phase queue to store * and hand out power. @@ -23,6 +22,15 @@ { public: /** + * Max power capacity of each network. + * Avoids possible overflows while providing a human friendly number. + */ + static const uint64 max_capacity; + + // number of fractional bits for network load values + static const uint8 FRACTION_PRECISION; + + /** * Must be called when a new map is started or loaded. Clears the table of networks. * @author Hj. Malthaner */ @@ -34,19 +42,17 @@ private: static slist_tpl powernet_list; - /// Max power capacity of each network, only purpose: avoid integer overflows - static const uint64 max_capacity; + // Network power supply. + uint64 power_supply; + // Network power demand. + uint64 power_demand; - /// Power supply in next step - uint64 next_supply; - /// Power supply in current step - uint64 this_supply; - /// Power demand in next step - uint64 next_demand; - /// Power demand in current step - uint64 this_demand; + // Computed normalized demand. + sint32 norm_demand; + // Computed normalized supply. + sint32 norm_supply; - /// Just transfers power demand and supply to current step + // Just transfers power demand and supply to current step void step(uint32 delta_t); public: @@ -55,17 +61,51 @@ uint64 get_max_capacity() const { return max_capacity; } - /// add to power supply for next step, respect max_capacity - void add_supply(const uint32 p); + /** + * Add power supply for next step. + */ + void add_supply(const uint32 p) { power_supply += (uint64)p; } - /// @returns current power supply - uint64 get_supply() const { return this_supply; } + /** + * Subtract power supply for next step. + */ + void sub_supply(const uint32 p) { power_supply -= (uint64)p; } - /// add to power demand for next step, respect max_capacity - void add_demand(const uint32 p); + /** + * Get the total power supply of the network. + */ + uint64 get_supply() const; - /// @returns current power demand - uint64 get_demand() const { return this_demand; } + /** + * Add power demand for next step. + */ + void add_demand(const uint32 p) { power_demand += (uint64)p; } + + /** + * Subtract power demand for next step. + */ + void sub_demand(const uint32 p) { power_demand -= (uint64)p; } + + /** + * Get the total power demand of the network. + */ + uint64 get_demand() const; + + /** + * Return the normalized value of demand in the network. + * Will have a logical value between 0 (no demand) and 1 (all supply consumed). + * Will have a logical value of 1 when no supply is present. + * Return value is fixed point with FRACTION_PRECISION fractional bits. + */ + sint32 get_normal_demand() const { return norm_demand; } + + /** + * Return the normalized value of supply in the network. + * Will have a logical value between 0 (no supply) and 1 (all demand supplied). + * Will have a logical value of 1 when no demand is present. + * Return value is fixed point with FRACTION_PRECISION fractional bits. + */ + sint32 get_normal_supply() const { return norm_supply; } }; #endif Index: trunk/obj/leitung2.cc =================================================================== --- trunk/obj/leitung2.cc (revision 8016) +++ trunk/obj/leitung2.cc (working copy) @@ -35,7 +35,11 @@ #include "../boden/grund.h" #include "../bauer/wegbauer.h" +const uint32 POWER_TO_MW = 12; +// use same precision as powernet +const uint8 leitung_t::FRACTION_PRECISION = powernet_t::FRACTION_PRECISION; + /** * returns possible directions for powerline on this tile */ @@ -335,19 +339,23 @@ { obj_t::info(buf); - const uint64 supply = get_net()->get_supply(); - const uint64 demand = get_net()->get_demand(); - const uint64 load = demand>supply ? supply:demand; + powernet_t * const net = get_net(); - buf.printf( translator::translate("Net ID: %lu\n"), (unsigned long)get_net() ); -// buf.printf( translator::translate("Capacity: %u MW\n"), (uint32)(get_net()->get_max_capacity()>>POWER_TO_MW) ); - buf.printf( translator::translate("Demand: %u MW\n"), (uint32)(demand>>POWER_TO_MW) ); - buf.printf( translator::translate("Generation: %u MW\n"), (uint32)(supply>>POWER_TO_MW) ); - buf.printf( translator::translate("Act. load: %u MW\n"), (uint32)(load>>POWER_TO_MW) ); - buf.printf( translator::translate("Usage: %u %%"), (uint32)((100ull*load)/(supply>0?supply:1ull)) ); + const uint64 supply = net->get_supply() >> POWER_TO_MW; + const uint64 demand = net->get_demand() >> POWER_TO_MW; + const uint32 usage = (uint32)((100 * net->get_normal_demand()) >> powernet_t::FRACTION_PRECISION); + + buf.printf(translator::translate("Net ID: %lu"), (unsigned long)net); + buf.printf("\n"); + //buf.printf(translator::translate("Capacity: %u MW"), (uint32)(net->get_max_capacity()>>POWER_TO_MW)); + //buf.printf("\n"); + buf.printf(translator::translate("Demand: %lu MW"), demand); + buf.printf("\n"); + buf.printf(translator::translate("Generation: %lu MW"), supply); + buf.printf("\n"); + buf.printf(translator::translate("Usage: %u %%"), usage); } - /** * Wird nach dem Laden der Welt aufgerufen - üblicherweise benutzt * um das Aussehen des Dings an Boden und Umgebung anzupassen @@ -376,34 +384,28 @@ player_t::add_maintenance(get_owner(), desc->get_wartung(), powerline_wt); } - -/** - * Speichert den Zustand des Objekts. - * - * @param file Zeigt auf die Datei, in die das Objekt geschrieben werden - * soll. - * @author Hj. Malthaner - */ void leitung_t::rdwr(loadsave_t *file) { xml_tag_t d( file, "leitung_t" ); - uint32 value; + obj_t::rdwr(file); - obj_t::rdwr(file); - if(file->is_saving()) { - value = (unsigned long)get_net(); + // no longer save power net pointer as it is no longer used + if( file->get_version() <= 120003 ) { + uint32 value; + if( file->is_saving() ) { + value = (uint32)get_net(); + } file->rdwr_long(value); } - else { - file->rdwr_long(value); + if( file->is_loading() ) { set_net(NULL); } if(get_typ()==leitung) { /* ATTENTION: during loading thus MUST not be called from the constructor!!! - * (Otherwise it will be always true! - */ + * (Otherwise it will be always true! + */ if(file->get_version() > 102002) { if(file->is_saving()) { const char *s = desc->get_name(); @@ -433,7 +435,6 @@ } } - // returns NULL, if removal is allowed // players can remove public owned powerlines const char *leitung_t::is_deletable(const player_t *player) @@ -467,7 +468,7 @@ pumpe_t::pumpe_t(loadsave_t *file ) : leitung_t( koord3d::invalid, NULL ) { fab = NULL; - supply = 0; + power_supply = 0; rdwr( file ); } @@ -475,7 +476,7 @@ pumpe_t::pumpe_t(koord3d pos, player_t *player) : leitung_t(pos, player) { fab = NULL; - supply = 0; + power_supply = 0; player_t::book_construction_costs(player, welt->get_settings().cst_transformer, get_pos().get_2d(), powerline_wt); } @@ -483,7 +484,7 @@ pumpe_t::~pumpe_t() { if(fab) { - fab->set_transformer_connected( false ); + fab->set_transformer_connected(NULL); fab = NULL; } pumpe_list.remove( this ); @@ -490,38 +491,75 @@ player_t::add_maintenance(get_owner(), (sint32)welt->get_settings().cst_maintain_transformer, powerline_wt); } - void pumpe_t::step(uint32 delta_t) { - if(fab==NULL) { + if( fab == NULL ) { return; } - - if( delta_t==0 ) { + else if( delta_t == 0 ) { return; } - supply = fab->get_power(); + // usage logic could go here - image_id new_image; - int winter_offset = 0; + // resolve image + uint16 winter_offset = 0; if( skinverwaltung_t::senke->get_count() > 3 && (get_pos().z >= welt->get_snowline() || welt->get_climate( get_pos().get_2d() ) == arctic_climate) ) { winter_offset = 2; } - if( supply > 0 ) { - get_net()->add_supply( supply ); - new_image = skinverwaltung_t::pumpe->get_image_id(1+winter_offset); + uint16 const image_offset = power_supply > 0 ? 1 : 0; + image_id const new_image = skinverwaltung_t::pumpe->get_image_id(image_offset + winter_offset); + + // update image + if( image != new_image ) { + set_flag(obj_t::dirty); + set_image(new_image); } - else { - new_image = skinverwaltung_t::pumpe->get_image_id(0+winter_offset); +} + +void pumpe_t::set_net(powernet_t * p) +{ + powernet_t * p_old = get_net(); + if( p_old != NULL ) { + p_old->sub_supply(power_supply); } - if(image!=new_image) { - set_flag(obj_t::dirty); - set_image( new_image ); + + leitung_t::set_net(p); + + if( p != NULL ) { + p->add_supply(power_supply); } } +void pumpe_t::set_power_supply(uint32 newsupply) +{ + // update power network + powernet_t *const p = get_net(); + if( p != NULL ) { + p->sub_supply(power_supply); + p->add_supply(newsupply); + } + power_supply = newsupply; +} + +sint32 pumpe_t::get_power_consumption() const +{ + powernet_t const *const p = get_net(); + return p->get_normal_demand(); +} + +void pumpe_t::rdwr(loadsave_t * file) { + xml_tag_t d( file, "pumpe_t" ); + + leitung_t::rdwr(file); + + // current power state + if( file->get_version() > 120003 ) { + file->rdwr_long(power_supply); + } +} + void pumpe_t::finish_rd() { leitung_t::finish_rd(); @@ -540,9 +578,10 @@ } if( fab ) { // only add when factory there - fab->set_transformer_connected( true ); + fab->set_transformer_connected(this); } } + #ifdef MULTI_THREAD pthread_mutex_lock( &pumpe_list_mutex ); #endif @@ -560,47 +599,67 @@ #endif } - void pumpe_t::info(cbuffer_t & buf) const { obj_t::info( buf ); - buf.printf( translator::translate("Net ID: %lu\n"), (unsigned long)get_net() ); - buf.printf( translator::translate("Generation: %u MW\n"), supply>>POWER_TO_MW ); - buf.printf("\n\n"); // pad for consistent dialog size + sint32 const usage = get_net()->get_normal_demand(); + + buf.printf( translator::translate("Net ID: %lu"), (unsigned long)get_net() ); + buf.printf("\n"); + buf.printf( translator::translate("Generation: %lu MW"), (uint64)(power_supply >> POWER_TO_MW) ); + buf.printf("\n"); + buf.printf( translator::translate("Usage: %u %%"), (uint32)((100 * usage) >> powernet_t::FRACTION_PRECISION) ); + buf.printf("\n"); // pad for consistent dialog size } -/************************************ From here on drain stuff ********************************************/ +/************************************ Distriubtion Transformer Code ********************************************/ slist_tpl senke_t::senke_list; +uint32 senke_t::payment_timer = 0; - void senke_t::new_world() { senke_list.clear(); + payment_timer = 0; } +void senke_t::static_rdwr(loadsave_t *file) +{ + if( file->get_version() > 120003 ) { + file->rdwr_long(payment_timer); + } +} void senke_t::step_all(uint32 delta_t) { + // payment period (could be tied to game setting) + const sint32 pay_period = PRODUCTION_DELTA_T * 10; // 10 seconds + + // revenue payout timer + payment_timer += delta_t; + const bool payout = payment_timer >= pay_period; + payment_timer %= pay_period; + + // step all distribution transformers FOR(slist_tpl, const s, senke_list) { s->step(delta_t); + if (payout) { + s->pay_revenue(); + } } } - senke_t::senke_t(loadsave_t *file) : leitung_t( koord3d::invalid, NULL ) { + energy_acc = 0; fab = NULL; - einkommen = 0; - max_einkommen = 1; + delta_sum = 0; next_t = 0; - delta_sum = 0; - next_power_demand = 0; - last_power_demand = 0; - power_load = 0; + rdwr( file ); + welt->sync.add(this); } @@ -608,13 +667,10 @@ senke_t::senke_t(koord3d pos, player_t *player) : leitung_t(pos, player) { fab = NULL; - einkommen = 0; - max_einkommen = 1; next_t = 0; delta_sum = 0; - next_power_demand = 0; - last_power_demand = 0; - power_load = 0; + power_demand = 0; + energy_acc = 0; player_t::book_construction_costs(player, welt->get_settings().cst_transformer, get_pos().get_2d(), powerline_wt); welt->sync.add(this); } @@ -622,9 +678,12 @@ senke_t::~senke_t() { + // one last final income + pay_revenue(); + welt->sync.remove( this ); if(fab!=NULL) { - fab->set_transformer_connected( false ); + fab->set_transformer_connected(NULL); fab = NULL; } senke_list.remove( this ); @@ -631,7 +690,6 @@ player_t::add_maintenance(get_owner(), (sint32)welt->get_settings().cst_maintain_transformer, powerline_wt); } - void senke_t::step(uint32 delta_t) { if( fab == NULL ) { @@ -641,51 +699,59 @@ return; } - // get solved power demand - last_power_demand = next_power_demand; + // energy metering logic + energy_acc += ((uint64)power_demand * (uint64)get_net()->get_normal_supply() * (uint64)delta_t) / ((uint64)PRODUCTION_DELTA_T << powernet_t::FRACTION_PRECISION); +} - // set current factory demand to be solved - next_power_demand = fab->get_power_demand(); - get_net()->add_demand( next_power_demand ); +void senke_t::pay_revenue() +{ + // megajoules (megawatt seconds) per cent + const uint64 mjpc = (1 << POWER_TO_MW) / 2; // should be tied to game setting - // compute power demand satisfaction from results - const uint64 net_demand = get_net()->get_demand(); - const uint64 net_supply = get_net()->get_supply(); - if( net_supply >= net_demand ) { - // demand fully satisfied - power_load = last_power_demand; + // calculate payment in cent + const sint64 payment = (sint64)(energy_acc / mjpc); + + // make payment + if( payment > 0 ) { + // enough has accumulated for a payment + get_owner()->book_revenue( payment, get_pos().get_2d(), powerline_wt ); + + // remove payment from accumulator + energy_acc %= mjpc; } - else if( last_power_demand > 0 ) { - // compute demand satisfaction, this is safe because if last_power_demand > 0 then net_demand > 0 - power_load = (uint32)((((uint64)last_power_demand) * ((net_supply << 5) / net_demand)) >> 5); +} + +void senke_t::set_net(powernet_t * p) +{ + powernet_t * p_old = get_net(); + if( p_old != NULL ) { + p_old->sub_demand(power_demand); } - else { - // no demand so no supply - power_load = 0; - } - // push back power and demand to factory - fab->add_power( power_load ); - fab->add_power_demand( last_power_demand - power_load ); + leitung_t::set_net(p); - // power payment logic - if( fab->get_desc()->get_electric_amount() == 65535 ) { - // demand not specified in pak, use old fixed demands - max_einkommen += last_power_demand * delta_t / PRODUCTION_DELTA_T; - einkommen += power_load * delta_t / PRODUCTION_DELTA_T; + if( p != NULL ) { + p->add_demand(power_demand); } - else { - max_einkommen += welt->inverse_scale_with_month_length( last_power_demand * delta_t / PRODUCTION_DELTA_T ); - einkommen += welt->inverse_scale_with_month_length( power_load * delta_t / PRODUCTION_DELTA_T ); +} + +void senke_t::set_power_demand(uint32 newdemand) +{ + // update power network + powernet_t *const p = get_net(); + if( p != NULL ) { + p->sub_demand(power_demand); + p->add_demand(newdemand); } - if( max_einkommen > (2000 << 11) ) { - get_owner()->book_revenue( einkommen >> 11, get_pos().get_2d(), powerline_wt ); - einkommen = 0; - max_einkommen = 1; - } + power_demand = newdemand; } +sint32 senke_t::get_power_satisfaction() const +{ + powernet_t const *const p = get_net(); + return p->get_normal_supply(); +} sync_result senke_t::sync_step(uint32 delta_t) { @@ -693,46 +759,48 @@ return SYNC_DELETE; } + // advance timers delta_sum += delta_t; - if( delta_sum > PRODUCTION_DELTA_T ) { - // sawtooth waveform resetting at PRODUCTION_DELTA_T => time period for image changing - delta_sum -= delta_sum - delta_sum % PRODUCTION_DELTA_T; - } + next_t += delta_t; - next_t += delta_t; + // change graphics at most 16 times a second if( next_t > PRODUCTION_DELTA_T / 16 ) { - // sawtooth waveform resetting at PRODUCTION_DELTA_T / 16 => image changes at most this fast - next_t -= next_t - next_t % (PRODUCTION_DELTA_T / 16); + // enforce timer periods + delta_sum %= PRODUCTION_DELTA_T; // 1 second + next_t %= PRODUCTION_DELTA_T / 16; // 1/16 seconds - image_id new_image; - int winter_offset = 0; - if( skinverwaltung_t::senke->get_count() > 3 && (get_pos().z >= welt->get_snowline() || welt->get_climate( get_pos().get_2d() ) == arctic_climate) ) { - winter_offset = 2; + // determine pwm period for image change + uint32 pwm_period = 0; + const sint32 satisfaction = get_net()->get_normal_supply(); + if( satisfaction >= 1 << powernet_t::FRACTION_PRECISION ) { + // always on + pwm_period = PRODUCTION_DELTA_T; } - if( last_power_demand > 0 ) { - uint32 load_factor = power_load * PRODUCTION_DELTA_T / last_power_demand; + else if( satisfaction >= ((7 << powernet_t::FRACTION_PRECISION) / 8) ) { + // limit to at most 7/8 of a second + pwm_period = 7 * PRODUCTION_DELTA_T / 8; + } + else if( satisfaction > ((1 << powernet_t::FRACTION_PRECISION) / 8) ) { + // duty cycle based on power satisfaction + pwm_period = (uint32)(((uint64)PRODUCTION_DELTA_T * (uint64)satisfaction) >> powernet_t::FRACTION_PRECISION); + } + else if( satisfaction > 0 ) { + // limit to at least 1/8 of a second + pwm_period = PRODUCTION_DELTA_T / 8; + } - // allow load factor to be 0, 1/8 to 7/8, 1 - // ensures power on image shows for low loads and ensures power off image shows for high loads - if( load_factor > 0 && load_factor < PRODUCTION_DELTA_T / 8 ) { - load_factor = PRODUCTION_DELTA_T / 8; - } - else { - if( load_factor > 7 * PRODUCTION_DELTA_T / 8 && load_factor < PRODUCTION_DELTA_T ) { - load_factor = 7 * PRODUCTION_DELTA_T / 8; - } - } + // determine image with PWM logic + const uint16 work_offset = (delta_sum < pwm_period) ? 1 : 0; - if( delta_sum <= (sint32)load_factor ) { - new_image = skinverwaltung_t::senke->get_image_id(1+winter_offset); - } - else { - new_image = skinverwaltung_t::senke->get_image_id(0+winter_offset); - } + // apply seasonal image offset + uint16 winter_offset = 0; + if( skinverwaltung_t::senke->get_count() > 3 && (get_pos().z >= welt->get_snowline() || + welt->get_climate(get_pos().get_2d()) == arctic_climate) ) { + winter_offset = 2; } - else { - new_image = skinverwaltung_t::senke->get_image_id(0+winter_offset); - } + + // update displayed image + image_id new_image = skinverwaltung_t::senke->get_image_id(work_offset + winter_offset); if( image != new_image ) { set_flag( obj_t::dirty ); set_image( new_image ); @@ -741,7 +809,19 @@ return SYNC_OK; } +void senke_t::rdwr(loadsave_t *file) +{ + xml_tag_t d( file, "senke_t" ); + leitung_t::rdwr(file); + + // current power state + if( file->get_version() > 120003 ) { + file->rdwr_longlong((sint64 &)energy_acc); + file->rdwr_long(power_demand); + } +} + void senke_t::finish_rd() { leitung_t::finish_rd(); @@ -759,9 +839,10 @@ fab = fabrik_t::get_fab(get_pos().get_2d()); } if( fab ) { - fab->set_transformer_connected( true ); + fab->set_transformer_connected(this); } } + #ifdef MULTI_THREAD pthread_mutex_lock( &senke_list_mutex ); #endif @@ -777,14 +858,16 @@ #endif } - void senke_t::info(cbuffer_t & buf) const { obj_t::info( buf ); - buf.printf( translator::translate("Net ID: %lu\n"), (unsigned long)get_net() ); - buf.printf( translator::translate("Demand: %u MW\n"), last_power_demand>>POWER_TO_MW ); - buf.printf( translator::translate("Act. load: %u MW\n"), power_load>>POWER_TO_MW ); - buf.printf( translator::translate("Supplied: %u %%"), (100*power_load)/(last_power_demand>0?last_power_demand:1) ); - buf.printf("\n\n"); // pad for consistent dialog size + sint32 const supplied = get_net()->get_normal_supply(); + + buf.printf( translator::translate("Net ID: %lu"), (unsigned long)get_net() ); + buf.printf("\n"); + buf.printf( translator::translate("Demand: %lu MW"), (uint64)(power_demand >> POWER_TO_MW)); + buf.printf("\n"); + buf.printf( translator::translate("Supplied: %u %%"), (uint32)((100 * supplied) >> powernet_t::FRACTION_PRECISION) ); + buf.printf("\n"); // pad for consistent dialog size } Index: trunk/obj/leitung2.h =================================================================== --- trunk/obj/leitung2.h (revision 8016) +++ trunk/obj/leitung2.h (working copy) @@ -15,7 +15,8 @@ #include "../simobj.h" #include "../tpl/slist_tpl.h" -#define POWER_TO_MW (12) // bitshift for converting internal power values to MW for display +// bitshift for converting internal power values to MW for display +extern const uint32 POWER_TO_MW; class powernet_t; class player_t; @@ -59,8 +60,15 @@ void calc_image(); public: + // number of fractional bits for network load values + static const uint8 FRACTION_PRECISION; + powernet_t* get_net() const { return net; } - void set_net(powernet_t* p) { net = p; } + /** + * Changes the currently registered power net. + * Can be overwritten to modify the power net on change. + */ + virtual void set_net(powernet_t* p) { net = p; } const way_desc_t * get_desc() { return desc; } void set_desc(const way_desc_t *new_desc) { desc = new_desc; } @@ -108,24 +116,10 @@ */ void calc_neighbourhood(); - /** - * Wird nach dem Laden der Welt aufgerufen - üblicherweise benutzt - * um das Aussehen des Dings an Boden und Umgebung anzupassen - * - * @author Hj. Malthaner - */ + virtual void rdwr(loadsave_t *file); virtual void finish_rd(); /** - * Speichert den Zustand des Objekts. - * - * @param file Zeigt auf die Datei, in die das Objekt geschrieben werden - * soll. - * @author Hj. Malthaner - */ - virtual void rdwr(loadsave_t *file); - - /** * @return NULL if OK, otherwise an error message * @author Hj. Malthaner */ @@ -144,8 +138,10 @@ static slist_tpl pumpe_list; fabrik_t *fab; - uint32 supply; + // The power supplied through the transformer + uint32 power_supply; + void step(uint32 delta_t); public: @@ -153,6 +149,24 @@ pumpe_t(koord3d pos, player_t *player); ~pumpe_t(); + virtual void set_net(powernet_t* p); + + /** + * Set the power supply of the transformer. + */ + void set_power_supply(uint32 newsupply); + + /** + * Get the power supply of the transformer. + */ + uint32 get_power_supply() const {return power_supply;} + + /** + * Get the normalized satisfaction value of the power consumed, updated every tick. + * Return value is fixed point with FRACTION_PRECISION fractional bits. + */ + sint32 get_power_consumption() const; + typ get_typ() const { return pumpe; } const char *get_name() const {return "Aufspanntransformator";} @@ -159,7 +173,8 @@ void info(cbuffer_t & buf) const; - void finish_rd(); + virtual void rdwr(loadsave_t *file); + virtual void finish_rd(); void calc_image() {} // otherwise it will change to leitung @@ -169,14 +184,7 @@ /* * Distribution transformers act as an interface between power networks and - * and power consuming factories. They work in a pipelined way by taking the - * energy demand of a factory, solving it between ticks and feeding the results - * back the next tick. - * - * This buffering means that the factory will get the results for a demand - * after 2 ticks, with the first tick getting the previous result. Any unfulfilled - * demand from a result will be refunded to the factory so that the factory can - * calculate how well demand is being fulfilled in general. + * and power consuming factories. */ class senke_t : public leitung_t, public sync_steppable { @@ -184,35 +192,68 @@ static void new_world(); static void step_all(uint32 delta_t); + /** + * Read and write static state. + * This is used to make payments occur at the same time after load as after saving. + */ + static void static_rdwr(loadsave_t *file); + private: + // List of all distribution transformers. static slist_tpl senke_list; - sint32 einkommen; - sint32 max_einkommen; + // Timer for global power payment. + static uint32 payment_timer; + fabrik_t *fab; - sint32 delta_sum; - sint32 next_t; - // the power demand to be solved next tick - uint32 next_power_demand; + // Pwm timer for duty cycling image. + uint32 delta_sum; - // the last power demand solved - uint32 last_power_demand; + // Timer for recalculating image. + uint32 next_t; - // the last power satisfaction computed - uint32 power_load; + // The power requested through the transformer. + uint32 power_demand; + // Energy accumulator (how much energy has been metered). + uint64 energy_acc; + void step(uint32 delta_t); + // Pay out revenue for the energy metered. + void pay_revenue(); + public: senke_t(loadsave_t *file); senke_t(koord3d pos, player_t *player); ~senke_t(); + virtual void set_net(powernet_t* p); + typ get_typ() const { return senke; } - // used to alternate between displaying power on and power off images at a frequency determined by the percentage of power supplied - // gives players a visual indication of a power network with insufficient generation + /** + * Set the power demand of the transformer. + */ + void set_power_demand(uint32 newdemand); + + /** + * Get the power demand of the transformer. + */ + uint32 get_power_demand() const {return power_demand;} + + /** + * Get the normalized satisfaction value of the power demand, updated every tick. + * Return value is fixed point with FRACTION_PRECISION fractional bits. + */ + sint32 get_power_satisfaction() const; + + /** + * Used to alternate between displaying power on and power off images. + * Frequency determined by the percentage of power supplied. + * Gives players a visual indication of a power network with insufficient generation. + */ sync_result sync_step(uint32 delta_t); const char *get_name() const {return "Abspanntransformator";} @@ -219,7 +260,8 @@ void info(cbuffer_t & buf) const; - void finish_rd(); + virtual void rdwr(loadsave_t *file); + virtual void finish_rd(); void calc_image() {} // otherwise it will change to leitung Index: trunk/simfab.cc =================================================================== --- trunk/simfab.cc (revision 8016) +++ trunk/simfab.cc (working copy) @@ -95,7 +95,22 @@ } }; +/** + * Produce a scaled production amount from a production amount and work factor. + */ +sint32 work_scale_production(sint32 prod, sint32 work){ + // compute scaled production, rounding up + return (sint32)((((sint64)prod * (sint64)work) + (1 << WORK_BITS) - 1) >> WORK_BITS); +} +/** + * Produce a work factor from a production amount and scaled production amount. + */ +sint32 work_from_production(sint32 prod, sint32 scaled){ + // compute work, rounding up + return (sint32)((((sint64)scaled << WORK_BITS) + (sint64)prod - 1) / (sint64)prod); +} + void ware_production_t::init_stats() { for( int m=0; m> PRODUCTION_SCALE_BITS); - - // Limit result to 1. - if( prod <= 1 ){ - return 1; - } - return prod; + return ramp_fact; } - void fabrik_t::arrival_statistics_t::init() { for( uint32 s=0; sis_electricity_producer() ) { + power = ((sint64)get_power_supply() * (sint64)get_power_consumption()) >> leitung_t::FRACTION_PRECISION; + } + else { + power = -(((sint64)get_power_demand() * (sint64)get_power_satisfaction()) >> leitung_t::FRACTION_PRECISION); + } weighted_sum_power += power * delta_time; set_stat( power, FAB_POWER ); } @@ -722,8 +738,6 @@ fabrik_t::fabrik_t(loadsave_t* file) { owner = NULL; - power = 0; - power_demand = 0; prodfactor_electric = 0; lieferziele_active_last_month = 0; pos = koord3d::invalid; @@ -757,7 +771,7 @@ total_input = total_transit = total_output = 0; status = nothing; currently_producing = false; - transformer_connected = false; + transformer = NULL; } @@ -784,9 +798,7 @@ menge_remainder = 0; activity_count = 0; currently_producing = false; - transformer_connected = false; - power = 0; - power_demand = 0; + transformer = NULL; total_input = total_transit = total_output = 0; status = nothing; lieferziele_active_last_month = 0; @@ -934,7 +946,7 @@ } // Does it consume? else if( !eingang.empty() ) { - control_type = desc->is_electricity_producer() ? CL_ELEC_CONS : CL_CONS_MANY; + control_type = CL_CONS_MANY; } // No I/O? else { @@ -959,7 +971,7 @@ // Boost logic determines what factors boost factory production. if( welt->get_settings().get_just_in_time() >= 2 ) { - if( !desc->is_electricity_producer() && desc->get_electric_amount() ) { + if( !desc->is_electricity_producer() && desc->get_electric_amount() > 0 ) { boost_type = BL_POWER; } else if( desc->get_pax_demand() || desc->get_mail_demand() ) { @@ -1291,12 +1303,13 @@ file->rdwr_long(adjusted_value); } - if( file->get_version() > 99016 ) { + // no longer save power at factories + if( 99016 < file->get_version() && file->get_version() <= 120003 ) { + sint32 power = 0; file->rdwr_long(power); } - - // Also save power demand. This is needed for dynamic power consumption to compute correct satisfaction. - if( file->get_version() > 120000 ){ + if( 120000 < file->get_version() && file->get_version() <= 120003 ) { + sint32 power_demand = 0; file->rdwr_long(power_demand); } @@ -1510,7 +1523,61 @@ return menge; } +void fabrik_t::set_power_supply(uint32 supply) +{ + pumpe_t *const trans = dynamic_cast(transformer); + if( trans == NULL ) { + return; + } + trans->set_power_supply(supply); +} +uint32 fabrik_t::get_power_supply() const +{ + pumpe_t *const trans = dynamic_cast(transformer); + if( trans == NULL ) { + return 0; + } + return trans->get_power_supply(); +} + +sint32 fabrik_t::get_power_consumption() const +{ + pumpe_t *const trans = dynamic_cast(transformer); + if( trans == NULL ) { + return 0; + } + return trans->get_power_consumption(); +} + +void fabrik_t::set_power_demand(uint32 demand) +{ + senke_t *const trans = dynamic_cast(transformer); + if( trans == NULL ) { + return; + } + trans->set_power_demand(demand); +} + +uint32 fabrik_t::get_power_demand() const +{ + senke_t *const trans = dynamic_cast(transformer); + if( trans == NULL ) { + return 0; + } + return trans->get_power_demand(); +} + +sint32 fabrik_t::get_power_satisfaction() const +{ + senke_t *const trans = dynamic_cast(transformer); + if( trans == NULL ) { + return 0; + } + return trans->get_power_satisfaction(); +} + + sint32 fabrik_t::input_vorrat_an(const ware_besch_t *typ) { sint32 menge = -1; @@ -1625,7 +1692,27 @@ return 0 < ( ( 1 << lieferziele.index_of(k) ) & lieferziele_active_last_month ); } +sint32 fabrik_t::get_jit2_power_boost() const +{ + // transformer boost amount + sint32 boost = 0; + // compute power boost + if( is_transformer_connected() ) { + sint32 const power_satisfaction = get_power_satisfaction(); + if( power_satisfaction >= ((sint32)1 << leitung_t::FRACTION_PRECISION) ) { + // all demand fulfilled + boost = (sint32)desc->get_electric_boost(); + } + else { + // calculate bonus, rounding down + boost = (sint32)(((sint64)desc->get_electric_boost() * (sint64)power_satisfaction) >> leitung_t::FRACTION_PRECISION); + } + } + + return boost; +} + void fabrik_t::step(uint32 delta_t) { // Only do something if advancing in time. @@ -1641,9 +1728,6 @@ // Actual production effort done. sint32 work = 0; - // The number of working units used. - sint32 wunits = 1; - // Desired production effort of factory. sint32 want = 0; @@ -1655,24 +1739,32 @@ // JIT1 implementation for power bonus. if( !desc->is_electricity_producer() && scaled_electric_amount > 0 ) { // one may be thinking of linking this to actual production only - prodfactor_electric = (sint32)( ( (sint64)(desc->get_electric_boost()) * (sint64)power + (sint64)(scaled_electric_amount >> 1) ) / (sint64)scaled_electric_amount ); + prodfactor_electric = (sint32)(((sint64)desc->get_electric_boost() * (sint64)get_power_satisfaction() + (sint64)(1 << (leitung_t::FRACTION_PRECISION - 1))) >> leitung_t::FRACTION_PRECISION); } break; } case BL_POWER: { - // Compute power boost based on how well power demand is being solved. - if( !transformer_connected ) { - // No transformer means no power bonus. - prodfactor_electric = 0; + // get desired power boost amount + sint32 const prodfactor_want = get_jit2_power_boost(); + + // calculate maximum change delta from change rate scaled by time + const sint32 prodfactor_change = max(BOOST_POWER_CHANGE_RATE * delta_t / PRODUCTION_DELTA_T, 1); + + // limit rate of change of electricity boost (improve stability) + const sint32 prodfactor_delta = prodfactor_want - prodfactor_electric; + if( prodfactor_delta > prodfactor_change ) { + // limit increase rate + prodfactor_electric += prodfactor_change; } - else if( power_demand == 0 ) { - // If all demand fulfilled then full bonus. - prodfactor_electric = (sint32)desc->get_electric_boost(); - } + //else if( prodfactor_delta < -prodfactor_change ) { + // limit decrease rate, off because of possible exploit + // prodfactor_electric -= prodfactor_change; + //} else { - // Calculate bonus from fraction of power satisfaction, rounding down. - prodfactor_electric = (sint32)( (sint64)desc->get_electric_boost() * (sint64)power / (sint64)(power + power_demand) ); + // no limit + prodfactor_electric = prodfactor_want; } + break; } default: { @@ -1702,6 +1794,8 @@ sint32 prod_comp; // The amount of consumption available. sint32 cons_comp = 0; + // power variable for legacy power logic + uint32 power; switch( control_type ) { case CL_PROD_CLASSIC: { @@ -1716,8 +1810,12 @@ if( menge_out > 0 ) { const sint32 p = (sint32)menge_out; - if( p > work ) { - work = p; + // compute work factor + const sint32 work_fact = work_from_production(prod, p); + + // work done is work done of maximum output + if( work_fact > work ) { + work = work_fact; } // produce @@ -1735,6 +1833,7 @@ } } } + break; } case CL_PROD_MANY: { @@ -1753,9 +1852,12 @@ continue; } - // Apply scaling. - prod_delta = ausgang[product].scale_production( prod ); + // get desired work factor + const sint32 work_fact = ausgang[product].get_work_factor(); + // compute desired production + prod_delta = work_scale_production(prod, work_fact); + // Cannot produce more than can be stored. if( prod_delta >= prod_comp ) { prod_delta = prod_comp; @@ -1769,11 +1871,12 @@ ausgang[product].menge += prod_delta; ausgang[product].book_stat((sint64)prod_delta * (sint64)desc->get_product(product)->get_factor(), FAB_GOODS_PRODUCED); - work += prod_delta; + work += work_fact; } - // Scale by number of units. - wunits = ausgang.get_count(); + // normalize work with respect to output number + work /= ausgang.get_count(); + break; } case CL_FACT_CLASSIC: { @@ -1838,8 +1941,10 @@ } } - work = consumed_menge; + // work done is consumption rate + work = work_from_production(prod, consumed_menge); } + break; } case CL_FACT_MANY: { @@ -1871,33 +1976,37 @@ continue; } - // Apply scaling. - prod_delta = ausgang[product].scale_production(prod); + // get desired work factor + const sint32 work_fact = ausgang[product].get_work_factor(); - // Cannot produce more than can be stored. + // compute desired production + prod_delta = work_scale_production(prod, work_fact); + + // limit to maximum storage if( prod_delta > prod_comp ) { prod_delta = prod_comp; } - // Assume each output is equally weighted when determining want. + // credit desired production for want want += prod_delta; - // Cannot produce anything without any input. + // skip producing anything if no input if( no_input ) { continue; } - // Apply input limiting. In case of insufficient supply earliest output gets priority. + // enforce input limits on production if( prod_delta > cons_comp ) { - // Need to limit production. + // not enough input prod_delta = cons_comp; cons_comp = 0; } else { + // enough input cons_comp -= prod_delta; } - // If filling to max, then register as inactive. + // register inactive outputs if( prod_delta == prod_comp ) { inactive_outputs++; if( inactive_outputs == ausgang.get_count() ) { @@ -1905,8 +2014,8 @@ } } - // Assume each output is equally weighted when determining work. - work += prod_delta; + // credit output work done + work += work_fact; // Produce output delta_menge += prod_delta; @@ -1914,23 +2023,24 @@ ausgang[product].book_stat((sint64)prod_delta * (sint64)desc->get_product(product)->get_factor(), FAB_GOODS_PRODUCED); } - // Want scaled by number of production units in factory. + // normalize want with respect to output number want /= ausgang.get_count(); - // Skip consumption if not enough input. + // skip consuming anything if no input if( no_input ) { break; } - // Work scaled by number of production units in factory. - wunits = ausgang.get_count(); + // normalize work with respect to output number + work /= ausgang.get_count(); - // Consume inputs. + // consume inputs + const sint32 consumed = work_scale_production(prod, work); for( uint32 index = 0; index < eingang.get_count(); index++ ) { - const sint32 consumed = work / wunits; eingang[index].menge -= consumed; eingang[index].book_stat((sint64)consumed * (sint64)desc->get_supplier(index)->get_consumption(), FAB_GOODS_CONSUMED); + // register inactive inputs if( eingang[index].menge <= 0 ) { currently_producing = false; eingang[index].menge = 0; @@ -1964,7 +2074,7 @@ // power station => produce power power += (uint32)( ((sint64)scaled_electric_amount * (sint64)(DEFAULT_PRODUCTION_FACTOR + prodfactor_pax + prodfactor_mail)) >> DEFAULT_PRODUCTION_FACTOR_BITS ); } - work += v; + work += 1 << WORK_BITS; // to find out, if storage changed delta_menge += v; } @@ -1974,16 +2084,21 @@ power += (uint32)( (((sint64)scaled_electric_amount * (sint64)(DEFAULT_PRODUCTION_FACTOR + prodfactor_pax + prodfactor_mail)) >> DEFAULT_PRODUCTION_FACTOR_BITS) * eingang[index].menge / (v + 1) ); } delta_menge += eingang[index].menge; - work += eingang[index].menge; + work += work_from_production(prod, eingang[index].menge); eingang[index].book_stat((sint64)eingang[index].menge * (sint64)desc->get_supplier(index)->get_consumption(), FAB_GOODS_CONSUMED); eingang[index].menge = 0; } } + + // normalize work with respect to input number + work /= eingang.get_count(); + break; } case CL_CONS_MANY: { // Consumer logic for many inputs. Work done is based on the average consumption of all inputs. - // Always want to consume prod. + + // always consume prod want = prod; // Do nothing if we cannot consume anything. @@ -1999,22 +2114,18 @@ continue; } - // Determine amount to consume. - if( eingang[index].menge > prod ) { - prod_delta = prod; - } - else { - prod_delta = eingang[index].menge; - } + // limit consumption to minimum of production or storage + prod_delta = eingang[index].menge > prod ? prod : eingang[index].menge; - // Add to work done. - work += prod_delta; + // add to work done + work += work_from_production(prod, prod_delta); - // Consume input. + // consume input delta_menge += prod_delta; eingang[index].menge -= prod_delta; eingang[index].book_stat((sint64)prod_delta * (sint64)desc->get_supplier(index)->get_consumption(), FAB_GOODS_CONSUMED); + // register inactive input if( eingang[index].menge <= 0 ) { inactive_inputs++; if( inactive_inputs == eingang.get_count() ) { @@ -2023,88 +2134,47 @@ } } - // Scale by number of units. - wunits = eingang.get_count(); + // normalize work with respect to input number + work /= eingang.get_count(); + + if( desc->is_electricity_producer() ) { + // compute power production + uint64 pp = ((uint64)scaled_electric_amount * (uint64)boost * (uint64)work) >> (DEFAULT_PRODUCTION_FACTOR_BITS + WORK_BITS); + set_power_supply((uint32)pp); + } + break; } case CL_ELEC_PROD: { // A simple no input electricity producer, like a solar array. - // Always maximum work done. + // always maximum work currently_producing = true; - work = prod; + work = 1 << WORK_BITS; delta_menge += prod; - // Produce maximum power scaled by boost. - power = (uint32)(((sint64)scaled_electric_amount * (sint64)boost) >> DEFAULT_PRODUCTION_FACTOR_BITS); + // compute power production + uint64 pp = ((uint64)scaled_electric_amount * (uint64)boost) >> DEFAULT_PRODUCTION_FACTOR_BITS; + set_power_supply((uint32)pp); + break; } case CL_ELEC_CLASSIC: { // Classic no input power producer. currently_producing = false; - work = prod; + work = 1 << WORK_BITS; // power station? => produce power if( desc->is_electricity_producer() ) { currently_producing = true; - power = (uint32)( ((sint64)scaled_electric_amount * (sint64)(DEFAULT_PRODUCTION_FACTOR + prodfactor_pax + prodfactor_mail)) >> DEFAULT_PRODUCTION_FACTOR_BITS ); + set_power_supply((uint32)( ((sint64)scaled_electric_amount * (sint64)(DEFAULT_PRODUCTION_FACTOR + prodfactor_pax + prodfactor_mail)) >> DEFAULT_PRODUCTION_FACTOR_BITS )); } break; } - case CL_ELEC_CONS: { - // A slightly more advanced power consumer. The scaled electric amount determines maximum output with each input providing an equal fraction. - - // Always want to consume prod. - want = prod; - - // Do nothing if we cannot consume anything. - if( inactive_inputs == eingang.get_count() ) { - break; - } - - currently_producing = true; - - for( uint32 index = 0; index < eingang.get_count(); index++ ) { - // Only process active inputs; - if( eingang[index].menge <= 0 ) { - break; - } - - // Determine amount to consume. - if( eingang[index].menge > prod ) { - prod_delta = prod; - } - else { - prod_delta = eingang[index].menge; - } - - // Add to work done. - work += prod_delta; - - // Consume input. - delta_menge += prod_delta; - eingang[index].menge -= prod_delta; - eingang[index].book_stat((sint64)prod_delta * (sint64)desc->get_supplier(index)->get_consumption(), FAB_GOODS_CONSUMED); - - if( eingang[index].menge == 0 ) { - inactive_inputs++; - if( inactive_inputs == eingang.get_count() ) { - currently_producing = false; - } - } - } - - // Scale by number of units. - wunits = eingang.get_count(); - - // Produce power scaled by boost. - power = (uint32)(((sint64)scaled_electric_amount * (sint64)boost * (sint64)work / ((sint64)prod * (sint64)wunits)) >> DEFAULT_PRODUCTION_FACTOR_BITS); - break; - } case CL_NONE: default: { // None always produces maximum for whatever reason. Also default. - work = prod; + work = 1 << WORK_BITS; break; } } @@ -2123,6 +2193,7 @@ for( uint32 index = 0; index < eingang.get_count(); index++ ) { eingang[index].demand_buffer += want; + // register inactive demand buffer if( eingang[index].demand_buffer >= eingang[index].max ) { inactive_demands++; } @@ -2145,6 +2216,7 @@ eingang[index].demand_buffer += want; + // register inactive demand buffer if( eingang[index].demand_buffer >= eingang[index].max ) { inactive_demands++; } @@ -2167,18 +2239,18 @@ case BL_CLASSIC: { // For compatibility purposes. Draw a fixed amount of power when "producing". if( !desc->is_electricity_producer() ) { - power = 0; if( currently_producing ) { // requires full power even if runs out of raw material next cycle - power_demand = scaled_electric_amount; + set_power_demand(scaled_electric_amount); } } break; } case BL_POWER: { - // Order power for work done scaled by boost amount. - power = 0; - power_demand = prod ? (uint32)(((sint64)scaled_electric_amount * (sint64)boost * (sint64)work / ((sint64)prod * (sint64)wunits)) >> DEFAULT_PRODUCTION_FACTOR_BITS) : 0; + // compute power demand + uint64 pd = ((uint64)scaled_electric_amount * (uint64)boost * (uint64)work) >> (DEFAULT_PRODUCTION_FACTOR_BITS + WORK_BITS); + set_power_demand((uint32)pd); + break; } default: { @@ -2538,6 +2610,13 @@ set_stat( prodfactor_electric, FAB_BOOST_ELECTRIC ); set_stat( prodfactor_pax, FAB_BOOST_PAX ); set_stat( prodfactor_mail, FAB_BOOST_MAIL ); + sint64 power; + if( desc->is_electricity_producer() ) { + power = ((sint64)get_power_supply() * (sint64)get_power_consumption()) >> leitung_t::FRACTION_PRECISION; + } + else { + power = -(((sint64)get_power_demand() * (sint64)get_power_satisfaction()) >> leitung_t::FRACTION_PRECISION); + } set_stat( power, FAB_POWER ); // since target cities' population may be increased -> re-apportion pax/mail demand @@ -2928,6 +3007,11 @@ eingang[in].placing_orders = (eingang[in].menge < eingang[in].max && eingang[in].get_in_transit() < eingang[in].max_transit); } } + + // set initial power boost + if ( boost_type == BL_POWER ) { + prodfactor_electric = get_jit2_power_boost(); + } } Index: trunk/simfab.h =================================================================== --- trunk/simfab.h (revision 8016) +++ trunk/simfab.h (working copy) @@ -23,6 +23,7 @@ class player_t; class stadt_t; class ware_t; +class leitung_t; /** @@ -77,16 +78,19 @@ /** * JIT2 output scale constants. */ -// The fixed point precision of production scale factors. -// Supported range 1 to 30. -static const uint32 PRODUCTION_SCALE_BITS = 10; +// The fixed point precision for work done fraction. +static const uint32 WORK_BITS = 16; // The minimum allowed production rate for a factory. This is to limit the time outputs take to fill completly (so factories idle sooner). // Fixed point form range must be between 0.0 and 1.0. -static const sint32 OUTPUT_SCALE_MINIMUM_FRACTION = (5 << PRODUCTION_SCALE_BITS) / 100; // ~5%, 1/20 of the full production rate. +static const sint32 WORK_SCALE_MINIMUM_FRACTION = (5 << WORK_BITS) / 100; // ~5%, 1/20 of the full production rate. // The number of times minimum_shipment must be in current storage before rampdown starts. -// Must be at least 2 to allow for full production. +// Must be at least 2 to allow for full production as shipment is not instant. static const sint32 OUTPUT_SCALE_RAMPDOWN_MULTIPLYER = 2; // Two shipments must be ready. +// The maximum rate at which power boost change change per second. +// This limit is required to help stiffen overloaded networks to combat oscilations caused by feedback. +static const sint32 BOOST_POWER_CHANGE_RATE = (5 << DEFAULT_PRODUCTION_FACTOR_BITS) / 100; // ~5% + /** * Shipment size constants. */ @@ -149,8 +153,11 @@ } void book_weighted_sum_storage(uint32 factor, sint64 delta_time); - // Utility methods. - sint32 scale_production(sint32 prod); + /** Get the recommended work factor for an output. + * Work factor ramps down as outputs fill. + * Returns a fixed point fraction to precision WORK_BITS. + */ + sint32 get_work_factor(); sint32 menge; // in internal units shifted by precision_bits (see step) sint32 max; @@ -220,11 +227,10 @@ CL_FACT_MANY, // Enhanced factory logic, consume at average of output rate or minimum input averaged. // Consumers are at the top of every supply chain. CL_CONS_CLASSIC, // Classic consumer logic. Can generate power. - CL_CONS_MANY, // Consumer that consumes multiple inputs. + CL_CONS_MANY, // Consumer that consumes multiple inputs, possibly produces power. // Electricity producers provider power. CL_ELEC_PROD, // Simple electricity source. (green energy) CL_ELEC_CLASSIC, // Classic electricity producer behaviour with no inputs. - CL_ELEC_CONS, // Power produced based on input satisfaction. } control_type; // Demand buffer order logic; @@ -281,7 +287,7 @@ vector_tpl fields; /** - * Die erzeugten waren auf die Haltestellen verteilen + * Distribute products to connected stops * @author Hj. Malthaner */ void verteile_waren(const uint32 product); @@ -295,19 +301,19 @@ const factory_desc_t *desc; /** - * Bauposition gedreht? + * Is construction site rotated? * @author V.Meyer */ uint8 rotate; /** - * productionsgrundmenge + * production base amount * @author Hj. Malthaner */ sint32 prodbase; /** - * multiplikator für die Produktionsgrundmenge + * multipliers for the production base amount * @author Hj. Malthaner */ sint32 prodfactor_electric; @@ -337,18 +343,12 @@ // Knightly : number of rounds where there is active production or consumption uint8 activity_count; - // true if the factory has a transformer adjacent - bool transformer_connected; + // The adjacent connected transformer, if any. + leitung_t *transformer; // true, if the factory did produce enough in the last step to require power bool currently_producing; - // power that can be currently drawn from this station (or the amount delivered) - uint32 power; - - // power requested for next step - uint32 power_demand; - uint32 total_input, total_transit, total_output; uint8 status; @@ -463,6 +463,36 @@ // scales the amount of production based on the amount already in storage uint32 scale_output_production(const uint32 product, uint32 menge) const; + /** + * Convenience method that deals with casting. + */ + void set_power_supply(uint32 supply); + + /** + * Convenience method that deals with casting. + */ + uint32 get_power_supply() const; + + /** + * Convenience method that deals with casting. + */ + sint32 get_power_consumption() const; + + /** + * Convenience method that deals with casting. + */ + void set_power_demand(uint32 demand); + + /** + * Convenience method that deals with casting. + */ + uint32 get_power_demand() const; + + /** + * Convenience method that deals with casting. + */ + sint32 get_power_satisfaction() const; + public: fabrik_t(loadsave_t *file); fabrik_t(koord3d pos, player_t* owner, const factory_desc_t* fabesch, sint32 initial_prod_base); @@ -556,27 +586,20 @@ sint32 input_vorrat_an(const ware_besch_t *ware); // Vorrat von Warentyp sint32 vorrat_an(const ware_besch_t *ware); // Vorrat von Warentyp - // returns all power and consume it to prevent multiple pumpes - uint32 get_power() { uint32 p=power; power=0; return p; } - - // returns power wanted by the factory for next step and sets to 0 to prevent multiple senkes on same powernet - uint32 get_power_demand() { uint32 p=power_demand; power_demand=0; return p; } - - // give power to the factory to consume ... - void add_power(uint32 p) { power += p; } - - // senkes give back wanted power they can't supply such that a senke on a different powernet can try suppling - // WARNING: senke stepping order can vary between ingame construction and savegame loading => different results after saveing/loading the game - void add_power_demand(uint32 p) { power_demand +=p; } - // true, if there was production requiring power in the last step bool is_currently_producing() const { return currently_producing; } - // used to limit transformers to 1 per factory and for controling power bonus. - bool is_transformer_connected() const { return transformer_connected; } - void set_transformer_connected(bool connected) { transformer_connected = connected; } + /** + * True if a transformer is connected to this factory. + */ + bool is_transformer_connected() const { return transformer != NULL; } /** + * Connect transformer to this factory. + */ + void set_transformer_connected(leitung_t *transformer) { this->transformer = transformer; } + + /** * @return 1 wenn consumption, * 0 wenn Produktionsstopp, * -1 wenn Ware nicht verarbeitet wird @@ -585,6 +608,11 @@ sint32 liefere_an(const ware_besch_t *, sint32 menge); + /** + * Calculate the JIT2 logic power boost amount using the currently attached transformer. + */ + sint32 get_jit2_power_boost() const; + void step(uint32 delta_t); // factory muss auch arbeiten void new_month(); Index: trunk/simutrans/history.txt =================================================================== --- trunk/simutrans/history.txt (revision 8016) +++ trunk/simutrans/history.txt (working copy) @@ -25,6 +25,13 @@ FIX: network core correctly enumerates local bind addresses instead of repeatedly trying to bind to the first local address ADD: support service names for ports in network URNs CHG: cities can now build on tiles with bridge pillars, matching the logic that pillared bridges can be placed over city buildings + FIX: incorrect scaling of electricity revenue with respect to month length + FIX: electricity related information is now consistent with respect to value source + FIX: electricity networks are no longer prone to arethmetic overflow which could result in incorrect graphics or income + FIX: electricity networks no longer skip a power tick or lose revenue accumulators during a save/load cycle + CHG: simplified transformer information UI removing redundant or meaningless values + CHG: factory power is solved in 1 tick instead of 2 + CHG: various improvements to JIT2 to improve factory and electricity network stability Release of 120.1.3: (r7753 on 2-Feb-2016): FIX: road vehicles search for new route when arriving at intersection and old route is invalid Index: trunk/simworld.cc =================================================================== --- trunk/simworld.cc (revision 8016) +++ trunk/simworld.cc (working copy) @@ -4074,11 +4074,11 @@ } finance_history_year[0][WORLD_FACTORIES] = finance_history_month[0][WORLD_FACTORIES] = fab_list.get_count(); - // step powerlines - required order: pumpe, senke, then powernet + // step powerlines - required order: powernet, pumpe then senke DBG_DEBUG4("karte_t::step", "step poweline stuff"); - pumpe_t::step_all( delta_t ); - senke_t::step_all( delta_t ); - powernet_t::step_all( delta_t ); + powernet_t::step_all(delta_t); + pumpe_t::step_all(delta_t); + senke_t::step_all(delta_t); DBG_DEBUG4("karte_t::step", "step players"); // then step all players @@ -4640,6 +4640,9 @@ file->rdwr_long(last_month); file->rdwr_long(last_year); + // rdwr satic states + senke_t::static_rdwr(file); + // rdwr cityrules for networkgames if(file->get_version()>102002) { bool do_rdwr = env_t::networkmode; @@ -5228,6 +5231,9 @@ active_player = players[0]; active_player_nr = 0; + // rdwr static states + senke_t::static_rdwr(file); + // rdwr cityrules, speedbonus for networkgames if(file->get_version()>102002) { bool do_rdwr = env_t::networkmode; @@ -5327,6 +5333,10 @@ win_set_world( this ); reliefkarte_t::get_karte()->init(); + // tick all power nets so that they update with loaded power + powernet_t::step_all(1); + + // load factories sint32 fabs; file->rdwr_long(fabs); DBG_MESSAGE("karte_t::laden()", "prepare for %i factories", fabs);