Index: dataobj/powernet.cc =================================================================== --- dataobj/powernet.cc (revision 7764) +++ 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 - FACTOR_PRECISION)) +const uint64 powernet_t::max_capacity = (uint64)4000000 << POWER_TO_MW; // (4 TW) +const uint8 powernet_t::FACTOR_PRECISION = 16; + + slist_tpl powernet_t::powernet_list; @@ -52,6 +55,9 @@ next_supply = 0; this_demand = 0; next_demand = 0; + + demand_factor = 1 << FACTOR_PRECISION; + supply_factor = 1 << FACTOR_PRECISION; } @@ -73,26 +79,37 @@ return; } + // limit supply + if( next_supply > max_capacity ) { + next_supply = max_capacity; + } + + // limit demand + if( next_demand > max_capacity ) { + next_demand = max_capacity; + } + + // advance values this_supply = next_supply; next_supply = 0; this_demand = next_demand; next_demand = 0; -} - -void powernet_t::add_supply(const uint32 p) -{ - next_supply += p; - if( next_supply>max_capacity ) { - next_supply = max_capacity; + // compute demand factor + if( (this_demand >= this_supply) || (this_supply == 0) ) { + demand_factor = 1 << FACTOR_PRECISION; } -} + else { + demand_factor = (sint32)((this_demand << FACTOR_PRECISION) / this_supply); + } + + // compute supply factor + if( (this_supply >= this_demand) || (this_demand == 0) ) { + supply_factor = 1 << FACTOR_PRECISION; - -void powernet_t::add_demand(const uint32 p) -{ - next_demand += p; - if( next_demand>max_capacity ) { - next_demand = max_capacity; } + else { + supply_factor = (sint32)((this_supply << FACTOR_PRECISION) / this_demand); + } + } Index: dataobj/powernet.h =================================================================== --- dataobj/powernet.h (revision 7764) +++ 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 factors + static const uint8 FACTOR_PRECISION; + + /** * Must be called when a new map is started or loaded. Clears the table of networks. * @author Hj. Malthaner */ @@ -34,9 +42,6 @@ private: static slist_tpl powernet_list; - /// Max power capacity of each network, only purpose: avoid integer overflows - static const uint64 max_capacity; - /// Power supply in next step uint64 next_supply; /// Power supply in current step @@ -46,6 +51,11 @@ /// Power demand in current step uint64 this_demand; + // cached demand factor value + sint32 demand_factor; + // cached supply factor value + sint32 supply_factor; + /// Just transfers power demand and supply to current step void step(uint32 delta_t); @@ -55,17 +65,47 @@ 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) { next_supply += (uint64)p; } + /** + * Subtract power supply for next step. + */ + void sub_supply(const uint32 p) { next_supply -= (uint64)p; } + /// @returns current power supply uint64 get_supply() const { return this_supply; } - /// add to power demand for next step, respect max_capacity - void add_demand(const uint32 p); + /** + * Add power demand for next step. + */ + void add_demand(const uint32 p) { next_demand += (uint64)p; } + /** + * Subtract power demand for next step. + */ + void sub_demand(const uint32 p) { next_demand -= (uint64)p; } + /// @returns current power demand uint64 get_demand() const { return this_demand; } + + /** + * Return the factor 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 FACTOR_PRECISION fractional bits. + */ + sint32 get_demand_factor() const { return demand_factor; } + + /** + * Return the factor 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 FACTOR_PRECISION fractional bits. + */ + sint32 get_supply_factor() const { return supply_factor; } }; #endif Index: obj/leitung2.cc =================================================================== --- obj/leitung2.cc (revision 7764) +++ obj/leitung2.cc (working copy) @@ -35,6 +35,7 @@ #include "../boden/grund.h" #include "../bauer/wegbauer.h" +const uint32 POWER_TO_MW = 12; /** * returns possible directions for powerline on this tile @@ -335,75 +336,41 @@ { 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_demand_factor()) >> powernet_t::FACTOR_PRECISION); - -/** - * Wird nach dem Laden der Welt aufgerufen - üblicherweise benutzt - * um das Aussehen des Dings an Boden und Umgebung anzupassen - * - * @author Hj. Malthaner - */ -void leitung_t::finish_rd() -{ -#ifdef MULTI_THREAD - pthread_mutex_lock( &verbinde_mutex ); -#endif - verbinde(); -#ifdef MULTI_THREAD - pthread_mutex_unlock( &verbinde_mutex ); -#endif -#ifdef MULTI_THREAD - pthread_mutex_lock( &calc_bild_mutex ); -#endif - calc_neighbourhood(); -#ifdef MULTI_THREAD - pthread_mutex_unlock( &calc_bild_mutex ); -#endif - grund_t *gr = welt->lookup(get_pos()); - assert(gr); (void)gr; - - player_t::add_maintenance(get_owner(), besch->get_wartung(), powerline_wt); + buf.printf(translator::translate("Net ID: %lu\n"), (unsigned long)net); + //buf.printf(translator::translate("Capacity: %u MW\n"), (uint32)(net->get_max_capacity()>>POWER_TO_MW)); + buf.printf(translator::translate("Demand: %u MW\n"), demand); + buf.printf(translator::translate("Generation: %u MW\n"), supply); + buf.printf(translator::translate("Usage: %u %%"), usage); } - -/** - * 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() <= 120001 ) { + 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 = besch->get_name(); @@ -433,7 +400,28 @@ } } +void leitung_t::finish_rd() +{ +#ifdef MULTI_THREAD + pthread_mutex_lock( &verbinde_mutex ); +#endif + verbinde(); +#ifdef MULTI_THREAD + pthread_mutex_unlock( &verbinde_mutex ); +#endif +#ifdef MULTI_THREAD + pthread_mutex_lock( &calc_bild_mutex ); +#endif + calc_neighbourhood(); +#ifdef MULTI_THREAD + pthread_mutex_unlock( &calc_bild_mutex ); +#endif + grund_t *gr = welt->lookup(get_pos()); + assert(gr); (void)gr; + player_t::add_maintenance(get_owner(), besch->get_wartung(), powerline_wt); +} + // returns NULL, if removal is allowed // players can remove public owned powerlines const char *leitung_t::is_deletable(const player_t *player) @@ -467,6 +455,7 @@ pumpe_t::pumpe_t(loadsave_t *file ) : leitung_t( koord3d::invalid, NULL ) { fab = NULL; + last_supply = 0; supply = 0; rdwr( file ); } @@ -475,6 +464,7 @@ pumpe_t::pumpe_t(koord3d pos, player_t *player) : leitung_t(pos, player) { fab = NULL; + last_supply = 0; supply = 0; player_t::book_construction_costs(player, welt->get_settings().cst_transformer, get_pos().get_2d(), powerline_wt); } @@ -490,7 +480,6 @@ 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) { @@ -501,15 +490,19 @@ return; } + last_supply = supply; + supply = fab->get_power(); + get_net()->add_supply(supply); + // usage logic could go here + image_id new_bild; int winter_offset = 0; if( skinverwaltung_t::senke->get_bild_anzahl() > 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 ); + if( last_supply > 0 ) { new_bild = skinverwaltung_t::pumpe->get_bild_nr(1+winter_offset); } else { @@ -521,7 +514,32 @@ } } +void pumpe_t::set_net(powernet_t * p) +{ + powernet_t * p_old = get_net(); + if( p_old != NULL ) { + p_old->sub_supply(supply); + } + leitung_t::set_net(p); + + if( p != NULL ) { + p->add_supply(supply); + } +} + + +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() > 120001 ) { + file->rdwr_long(supply); + } +} + void pumpe_t::finish_rd() { leitung_t::finish_rd(); @@ -543,6 +561,7 @@ fab->set_transformer_connected( true ); } } + #ifdef MULTI_THREAD pthread_mutex_lock( &pumpe_list_mutex ); #endif @@ -565,42 +584,66 @@ { obj_t::info( buf ); + const sint32 usage = get_net()->get_demand_factor(); + 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( translator::translate("Generation: %u MW\n"), last_supply>>POWER_TO_MW ); + buf.printf( translator::translate("Usage: %u %%"), (uint32)((100 * usage) >> powernet_t::FACTOR_PRECISION) ); buf.printf("\n\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::neue_karte() { senke_list.clear(); + payment_timer = 0; } +void senke_t::static_rdwr(loadsave_t *file) +{ + file->rdwr_long(payment_timer); +} void senke_t::step_all(uint32 delta_t) { + // step all distribution transformers FOR(slist_tpl, const s, senke_list) { s->step(delta_t); } + + // payment period in seconds + const sint32 pay_period = PRODUCTION_DELTA_T * 10; // could be tied to game setting + + // revenue payout timer + payment_timer += delta_t; + if( payment_timer >= pay_period ) { + // pay out all transformer revenue + FOR(slist_tpl, const s, senke_list) { + s->pay_revenue(); + } + + // enforce timer period + payment_timer %= pay_period; + } } - 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,8 +651,7 @@ senke_t::senke_t(koord3d pos, player_t *player) : leitung_t(pos, player) { fab = NULL; - einkommen = 0; - max_einkommen = 1; + energy_acc = 0; next_t = 0; delta_sum = 0; next_power_demand = 0; @@ -622,6 +664,9 @@ senke_t::~senke_t() { + // one last final income + pay_revenue(); + welt->sync.remove( this ); if(fab!=NULL) { fab->set_transformer_connected( false ); @@ -631,7 +676,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 ) { @@ -644,49 +688,63 @@ // get solved power demand last_power_demand = next_power_demand; - // set current factory demand to be solved + // register current demand with powernet next_power_demand = fab->get_power_demand(); - get_net()->add_demand( next_power_demand ); + get_net()->add_demand(next_power_demand); + // get power satisfaction factor + const sint32 supplied = get_net()->get_supply_factor(); + // 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 ) { + if( supplied >= 1 << powernet_t::FACTOR_PRECISION ) { // demand fully satisfied power_load = last_power_demand; } - else if( last_power_demand > 0 ) { + else { // 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); + power_load = (uint32)(((uint64)last_power_demand * (uint64)supplied) >> powernet_t::FACTOR_PRECISION); } - 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 ); - // power payment logic - if( fab->get_besch()->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; + // energy metering logic + energy_acc += (uint32)((uint64)power_load * (uint64)delta_t / (uint64)PRODUCTION_DELTA_T); +} + +void senke_t::pay_revenue() +{ + // megajoules (megawatt seconds) per cent + const uint64 mjpc = (1 << POWER_TO_MW) / 2; // should be tied to game setting + + // 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 { - 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_net(powernet_t * p) +{ + powernet_t * p_old = get_net(); + if( p_old != NULL ) { + p_old->sub_demand(next_power_demand); } - if( max_einkommen > (2000 << 11) ) { - get_owner()->book_revenue( einkommen >> 11, get_pos().get_2d(), powerline_wt ); - einkommen = 0; - max_einkommen = 1; + leitung_t::set_net(p); + + if( p != NULL ) { + p->add_demand(next_power_demand); } } - sync_result senke_t::sync_step(uint32 delta_t) { if(fab==NULL) { @@ -693,46 +751,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_bild; - int winter_offset = 0; - if( skinverwaltung_t::senke->get_bild_anzahl() > 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_supply_factor(); + if( satisfaction >= 1 << powernet_t::FACTOR_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::FACTOR_PRECISION) / 8) ) { + // limit to at most 7/8 of a second + pwm_period = 7 * PRODUCTION_DELTA_T / 8; + } + else if( satisfaction > ((1 << powernet_t::FACTOR_PRECISION) / 8) ) { + // duty cycle based on power satisfaction + pwm_period = (uint32)(((uint64)PRODUCTION_DELTA_T * (uint64)satisfaction) >> powernet_t::FACTOR_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_bild = skinverwaltung_t::senke->get_bild_nr(1+winter_offset); - } - else { - new_bild = skinverwaltung_t::senke->get_bild_nr(0+winter_offset); - } + // apply seasonal image offset + uint16 winter_offset = 0; + if( skinverwaltung_t::senke->get_bild_anzahl() > 3 && (get_pos().z >= welt->get_snowline() || + welt->get_climate(get_pos().get_2d()) == arctic_climate) ) { + winter_offset = 2; } - else { - new_bild = skinverwaltung_t::senke->get_bild_nr(0+winter_offset); - } + + // update displayed image + image_id new_bild = skinverwaltung_t::senke->get_bild_nr(work_offset + winter_offset); if( bild != new_bild ) { set_flag( obj_t::dirty ); set_bild( new_bild ); @@ -741,7 +801,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() > 120001 ) { + file->rdwr_longlong((sint64 &)energy_acc); + file->rdwr_long(next_power_demand); + } +} + void senke_t::finish_rd() { leitung_t::finish_rd(); @@ -762,6 +834,7 @@ fab->set_transformer_connected( true ); } } + #ifdef MULTI_THREAD pthread_mutex_lock( &senke_list_mutex ); #endif @@ -777,14 +850,15 @@ #endif } - void senke_t::info(cbuffer_t & buf) const { obj_t::info( buf ); + const sint32 supplied = get_net()->get_supply_factor(); + 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( translator::translate("Supplied: %u %%"), (uint32)((100 * supplied) >> powernet_t::FACTOR_PRECISION) ); buf.printf("\n\n"); // pad for consistent dialog size } Index: obj/leitung2.h =================================================================== --- obj/leitung2.h (revision 7764) +++ 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; @@ -60,7 +61,11 @@ public: 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 weg_besch_t * get_besch() { return besch; } void set_besch(const weg_besch_t *new_besch) { besch = new_besch; } @@ -108,24 +113,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 +135,13 @@ static slist_tpl pumpe_list; fabrik_t *fab; + + // the power supply to be solved next tick uint32 supply; + // the power supply that was last solved + uint32 last_supply; + void step(uint32 delta_t); public: @@ -153,6 +149,8 @@ pumpe_t(koord3d pos, player_t *player); ~pumpe_t(); + virtual void set_net(powernet_t* p); + typ get_typ() const { return pumpe; } const char *get_name() const {return "Aufspanntransformator";} @@ -159,7 +157,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 @@ -184,15 +183,30 @@ static void neue_karte(); 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; + + // energy accumulator (how much energy has been metered) + uint64 energy_acc; + fabrik_t *fab; - sint32 delta_sum; - sint32 next_t; + // pwm timer for duty cycling image + uint32 delta_sum; + + // timer for recalculating image + uint32 next_t; + // the power demand to be solved next tick uint32 next_power_demand; @@ -204,15 +218,23 @@ 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 + /** + * 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 +241,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: simfab.cc =================================================================== --- simfab.cc (revision 7764) +++ 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_loading() && (welt->get_settings().get_just_in_time() >= 2) ){ + prodfactor_electric = besch->get_electric_boost(); + } + delta_sum = 0; delta_menge = 0; menge_remainder = 0; @@ -931,7 +945,7 @@ } // Does it consume? else if( !eingang.empty() ) { - control_type = besch->is_electricity_producer() ? CL_ELEC_CONS : CL_CONS_MANY; + control_type = CL_CONS_MANY; } // No I/O? else { @@ -1620,9 +1634,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; @@ -1639,19 +1650,41 @@ break; } case BL_POWER: { + // get desired power boost amount + sint32 prodfactor_want; + // Compute power boost based on how well power demand is being solved. if( !transformer_connected ) { // No transformer means no power bonus. - prodfactor_electric = 0; + prodfactor_want = 0; } else if( power_demand == 0 ) { // If all demand fulfilled then full bonus. - prodfactor_electric = (sint32)besch->get_electric_boost(); + prodfactor_want = (sint32)besch->get_electric_boost(); } else { // Calculate bonus from fraction of power satisfaction, rounding down. - prodfactor_electric = (sint32)( (sint64)besch->get_electric_boost() * (sint64)power / (sint64)(power + power_demand) ); + prodfactor_want = (sint32)( (sint64)besch->get_electric_boost() * (sint64)power / (sint64)(power + power_demand) ); } + + // 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( prodfactor_delta < -prodfactor_change ) { + // limit decrease rate, off because of possible exploit + // prodfactor_electric -= prodfactor_change; + //} + else { + // no limit + prodfactor_electric = prodfactor_want; + } + break; } default: { @@ -1695,8 +1728,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 @@ -1714,6 +1751,7 @@ } } } + break; } case CL_PROD_MANY: { @@ -1732,9 +1770,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; @@ -1748,11 +1789,12 @@ ausgang[product].menge += prod_delta; ausgang[product].book_stat((sint64)prod_delta * (sint64)besch->get_produkt(product)->get_faktor(), 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: { @@ -1817,8 +1859,10 @@ } } - work = consumed_menge; + // work done is consumption rate + work = work_from_production(prod, consumed_menge); } + break; } case CL_FACT_MANY: { @@ -1850,33 +1894,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() ) { @@ -1884,8 +1932,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; @@ -1893,23 +1941,24 @@ ausgang[product].book_stat((sint64)prod_delta * (sint64)besch->get_produkt(product)->get_faktor(), 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)besch->get_lieferant(index)->get_verbrauch(), FAB_GOODS_CONSUMED); + // register inactive inputs if( eingang[index].menge <= 0 ) { currently_producing = false; eingang[index].menge = 0; @@ -1943,7 +1992,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; } @@ -1953,16 +2002,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)besch->get_lieferant(index)->get_verbrauch(), 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. @@ -1978,22 +2032,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)besch->get_lieferant(index)->get_verbrauch(), FAB_GOODS_CONSUMED); + // register inactive input if( eingang[index].menge <= 0 ) { inactive_inputs++; if( inactive_inputs == eingang.get_count() ) { @@ -2002,26 +2052,51 @@ } } - // Scale by number of units. - wunits = eingang.get_count(); + // normalize work with respect to input number + work /= eingang.get_count(); + + if( besch->is_electricity_producer() ) { + // compute power production + uint64 pp = ((uint64)scaled_electric_amount * (uint64)boost * (uint64)work) >> (DEFAULT_PRODUCTION_FACTOR_BITS + WORK_BITS); + + // supply power + if( pp >= (uint64)MAX_POWER_IO ) { + // supply clamped at maximum + power = MAX_POWER_IO; + } + else { + power = (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; + + // supply power + if( pp >= (uint64)MAX_POWER_IO ) { + // supply clamped at maximum + power = MAX_POWER_IO; + } + else { + power = (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( besch->is_electricity_producer() ) { @@ -2030,60 +2105,10 @@ } 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)besch->get_lieferant(index)->get_verbrauch(), 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; } } @@ -2102,6 +2127,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++; } @@ -2124,6 +2150,7 @@ eingang[index].demand_buffer += want; + // register inactive demand buffer if( eingang[index].demand_buffer >= eingang[index].max ) { inactive_demands++; } @@ -2155,9 +2182,20 @@ break; } case BL_POWER: { - // Order power for work done scaled by boost amount. + // power ordered based on work done 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); + + // order power + if( pd >= (uint64)MAX_POWER_IO ) { + // demand clamped at maximum + power_demand = MAX_POWER_IO; + } + else { + power_demand = (uint32)pd; + } break; } default: { Index: simfab.h =================================================================== --- simfab.h (revision 7764) +++ simfab.h (working copy) @@ -77,16 +77,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 +152,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; @@ -196,6 +202,12 @@ */ enum { precision_bits = 10, old_precision_bits = 10, precision_mask = 1023 }; + /** + * The maximum power to and from a transformer per tick. + * This should probably be a human readable value rather than physical limit. + */ + static const uint32 MAX_POWER_IO = (uint32)-1; + private: /** * Factory statistics @@ -220,11 +232,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 +292,7 @@ vector_tpl fields; /** - * Die erzeugten waren auf die Haltestellen verteilen + * Distribute products to connected stops * @author Hj. Malthaner */ void verteile_waren(const uint32 produkt); @@ -295,19 +306,19 @@ const fabrik_besch_t *besch; /** - * Bauposition gedreht? + * Is construction site rotated? * @author V.Meyer */ uint8 rotate; /** - * produktionsgrundmenge + * 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; Index: simworld.cc =================================================================== --- simworld.cc (revision 7764) +++ simworld.cc (working copy) @@ -4307,11 +4307,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"); + powernet_t::step_all( delta_t ); pumpe_t::step_all( delta_t ); senke_t::step_all( delta_t ); - powernet_t::step_all( delta_t ); DBG_DEBUG4("karte_t::step", "step players"); // then step all players @@ -4872,6 +4872,11 @@ file->rdwr_long(last_month); file->rdwr_long(last_year); + // rdwr senke payment status + if( file->get_version() > 120001 ) { + senke_t::static_rdwr(file); + } + // rdwr cityrules for networkgames if(file->get_version()>102002) { bool do_rdwr = env_t::networkmode; @@ -5450,6 +5455,11 @@ active_player = players[0]; active_player_nr = 0; + // rdwr senke payment status + if( file->get_version() > 120001 ) { + senke_t::static_rdwr(file); + } + // rdwr cityrules, speedbonus for networkgames if(file->get_version()>102002) { bool do_rdwr = env_t::networkmode;