Implements "static" area machinery power usage

- Instead of using auto_use_power to re-tally up machinery's power usage every cycle, track the steady "static" load separately from the transient "oneoff" usage.  Machines then only need to inform the area when they use oneoff power or *change* their steady usage.
- Remove auto_use_power and stop SSmachines from calling it.
- Add vars to track "static" usage for each of the three power channels to /area
- Rename the existing three vars to "oneoff" so its clear what they mean (and to catch people accidentally updating them directly)
- Update area power procs and APCs to use the new variables.
- Rename /area/proc/use_power() to use_power_oneoff() to make it clear what it is doing.
- Deprecate /obj/machinery/use_power() in favor of use_power_oneoff() but don't delete yet.  Can transition gradually.
- Add logic to the update_power procs on machines to calculate the deltas and update static area power whenever their usage changes.
- Add logic to machines to update area power when they are created, destroyed, or move.
- Moved /obj/machinery procs related to area power usage into machinery_power.dm to make them easier to find.
- Added or updated comments in several places to explain what is going on and how to use it.
This commit is contained in:
Leshana
2020-04-19 19:25:27 -04:00
parent 7d5042cacd
commit ccef6cc908
15 changed files with 287 additions and 142 deletions

View File

@@ -18,6 +18,7 @@ var/global/defer_powernet_rebuild = 0 // True if net rebuild will be called
#define USE_POWER_ACTIVE 2 // Machine is using power at its active power level
// Channel numbers for power.
#define CURRENT_CHANNEL -1 // Passed as an argument this means "use whatever current channel is"
#define EQUIP 1
#define LIGHT 2
#define ENVIRON 3

View File

@@ -121,10 +121,8 @@ SUBSYSTEM_DEF(machines)
while(current_run.len)
var/obj/machinery/M = current_run[current_run.len]
current_run.len--
if(istype(M) && !QDELETED(M) && !(M.process(wait) == PROCESS_KILL))
if(M.use_power)
M.auto_use_power()
else
if(!istype(M) || QDELETED(M) || (M.process(wait) == PROCESS_KILL))
global.processing_machines.Remove(M)
if(!QDELETED(M))
DISABLE_BITFIELD(M.datum_flags, DF_ISPROCESSING)

View File

@@ -21,14 +21,22 @@
var/requires_power = 1
var/always_unpowered = 0 //this gets overriden to 1 for space in area/New()
var/power_equip = 1
var/power_light = 1
var/power_environ = 1
var/music = null
var/used_equip = 0
var/used_light = 0
var/used_environ = 0
// Power channel status - Is it currently energized?
var/power_equip = TRUE
var/power_light = TRUE
var/power_environ = TRUE
// Oneoff power usage - Used once and cleared each power cycle
var/oneoff_equip = 0
var/oneoff_light = 0
var/oneoff_environ = 0
// Continuous "static" power usage - Do not update these directly!
var/static_equip = 0
var/static_light = 0
var/static_environ = 0
var/music = null
var/has_gravity = 1
var/secret_name = FALSE // This tells certain things that display areas' names that they shouldn't display this area's name.
var/obj/machinery/power/apc/apc = null
@@ -81,7 +89,7 @@
for(var/atom/movable/AM in T)
A.Entered(AM, old_area)
for(var/obj/machinery/M in T)
M.power_change()
M.area_changed(old_area, A)
/area/proc/get_contents()
return contents
@@ -239,32 +247,66 @@
if (fire || eject || party)
updateicon()
/area/proc/usage(var/chan)
/area/proc/usage(var/chan, var/include_static = TRUE)
var/used = 0
switch(chan)
if(LIGHT)
used += used_light
used += oneoff_light + (include_static * static_light)
if(EQUIP)
used += used_equip
used += oneoff_equip + (include_static * static_equip)
if(ENVIRON)
used += used_environ
used += oneoff_environ + (include_static * static_environ)
if(TOTAL)
used += used_light + used_equip + used_environ
used += oneoff_light + (include_static * static_light)
used += oneoff_equip + (include_static * static_equip)
used += oneoff_environ + (include_static * static_environ)
return used
// Helper for APCs; will generally be called every tick.
/area/proc/clear_usage()
used_equip = 0
used_light = 0
used_environ = 0
oneoff_equip = 0
oneoff_light = 0
oneoff_environ = 0
/area/proc/use_power(var/amount, var/chan)
// Use this for a one-time power draw from the area, typically for non-machines.
/area/proc/use_power_oneoff(var/amount, var/chan)
switch(chan)
if(EQUIP)
used_equip += amount
oneoff_equip += amount
if(LIGHT)
used_light += amount
oneoff_light += amount
if(ENVIRON)
used_environ += amount
oneoff_environ += amount
// This is used by machines to properly update the area of power changes.
/area/proc/power_use_change(old_amount, new_amount, chan)
use_power_static(new_amount - old_amount, chan) // Simultaneously subtract old_amount and add new_amount.
// Not a proc you want to use directly unless you know what you are doing; see use_power_oneoff below instead.
/area/proc/use_power_static(var/amount, var/chan)
switch(chan)
if(EQUIP)
static_equip += amount
if(LIGHT)
static_light += amount
if(ENVIRON)
static_environ += amount
// This recomputes the continued power usage; can be used for testing or error recovery, but is not called every tick.
/area/proc/retally_power()
static_equip = 0
static_light = 0
static_environ = 0
for(var/obj/machinery/M in src)
switch(M.power_channel)
if(EQUIP)
static_equip += M.get_power_usage()
if(LIGHT)
static_light += M.get_power_usage()
if(ENVIRON)
static_environ += M.get_power_usage()
//////////////////////////////////////////////////////////////////
var/list/mob/living/forced_ambiance_list = new

View File

@@ -5,12 +5,16 @@ Overview:
of 'Del' removes reference to src machine in global 'machines list'.
Class Variables:
power_init_complete (boolean)
Indicates that we have have registered our static power usage with the area.
use_power (num)
current state of auto power use.
Possible Values:
0 -- no auto power use
1 -- machine is using power at its idle power level
2 -- machine is using power at its active power level
USE_POWER_OFF:0 -- no auto power use
USE_POWER_IDLE:1 -- machine is using power at its idle power level
USE_POWER_ACTIVE:2 -- machine is using power at its active power level
active_power_usage (num)
Value for the amount of power to use when in active power mode
@@ -51,28 +55,20 @@ Class Procs:
Destroy() 'game/machinery/machine.dm'
auto_use_power() 'game/machinery/machine.dm'
This proc determines how power mode power is deducted by the machine.
'auto_use_power()' is called by the 'master_controller' game_controller every
tick.
get_power_usage() 'game/machinery/machinery_power.dm'
Returns the amount of power this machine uses every SSmachines cycle.
Default definition uses 'use_power', 'active_power_usage', 'idle_power_usage'
Return Value:
return:1 -- if object is powered
return:0 -- if object is not powered.
Default definition uses 'use_power', 'power_channel', 'active_power_usage',
'idle_power_usage', 'powered()', and 'use_power()' implement behavior.
powered(chan = EQUIP) 'modules/power/power.dm'
powered(chan = CURRENT_CHANNEL) 'game/machinery/machinery_power.dm'
Checks to see if area that contains the object has power available for power
channel given in 'chan'.
use_power(amount, chan=EQUIP, autocalled) 'modules/power/power.dm'
use_power_oneoff(amount, chan=CURRENT_CHANNEL) 'game/machinery/machinery_power.dm'
Deducts 'amount' from the power channel 'chan' of the area that contains the object.
If it's autocalled then everything is normal, if something else calls use_power we are going to
need to recalculate the power two ticks in a row.
power_change() 'modules/power/power.dm'
power_change() 'game/machinery/machinery_power.dm'
Called by the area that contains the object when ever that area under goes a
power state change (area runs out of power, or area channel is turned off).
@@ -108,6 +104,7 @@ Class Procs:
var/idle_power_usage = 0
var/active_power_usage = 0
var/power_channel = EQUIP //EQUIP, ENVIRON or LIGHT
var/power_init_complete = FALSE
var/list/component_parts = null //list of all the parts used to build it, if made from certain kinds of frames.
var/uid
var/panel_open = 0
@@ -149,7 +146,9 @@ Class Procs:
if(A.loc == src) // If the components are inside the machine, delete them.
qdel(A)
else // Otherwise we assume they were dropped to the ground during deconstruction, and were not removed from the component_parts list by deconstruction code.
component_parts -= A
warning("[A] was still in [src]'s component_parts when it was Destroy()'d")
component_parts.Cut()
component_parts = null
if(contents) // The same for contents.
for(var/atom/A in contents)
if(ishuman(A))
@@ -161,8 +160,7 @@ Class Procs:
qdel(A)
return ..()
/obj/machinery/process()//If you dont use process or power why are you here
if(!(use_power || idle_power_usage || active_power_usage))
/obj/machinery/process() // Steady power usage is handled separately. If you dont use process why are you here?
return PROCESS_KILL
/obj/machinery/emp_act(severity)
@@ -196,31 +194,6 @@ Class Procs:
else
return
//sets the use_power var and then forces an area power update
/obj/machinery/proc/update_use_power(var/new_use_power)
use_power = new_use_power
// Sets the power_channel var
/obj/machinery/proc/update_power_channel(var/new_channel)
power_channel = new_channel
// Sets the idle_power_usage var
/obj/machinery/proc/update_idle_power_usage(var/new_power_usage)
idle_power_usage = new_power_usage
// Sets the active_power_usage var
/obj/machinery/proc/update_active_power_usage(var/new_power_usage)
active_power_usage = new_power_usage
/obj/machinery/proc/auto_use_power()
if(!powered(power_channel))
return 0
if(use_power == USE_POWER_IDLE)
use_power(idle_power_usage, power_channel, 1)
else if(use_power >= USE_POWER_ACTIVE)
use_power(active_power_usage, power_channel, 1)
return 1
/obj/machinery/vv_edit_var(var/var_name, var/new_value)
if(var_name == NAMEOF(src, use_power))
update_use_power(new_value)

View File

@@ -0,0 +1,183 @@
//
// /obj/machinery POWER USAGE CODE HERE! GO GO GADGET STATIC POWER!
// Note: You can dinf /obj/machinery/power power usage code in power.dm
//
// The following four machinery variables determine the "static" amount of power used every power cycle:
// - use_power, idle_power_usage, active_power_usage, power_channel
//
// Please never change any of these variables! Use the procs that update them instead!
//
// Note that we update the area even if the area is unpowered.
#define REPORT_POWER_CONSUMPTION_CHANGE(old_power, new_power)\
if(old_power != new_power){\
var/area/A = get_area(src);\
if(A) A.power_use_change(old_power, new_power, power_channel)}
// Current power consumption right now.
#define POWER_CONSUMPTION (use_power == USE_POWER_IDLE ? idle_power_usage : (use_power >= USE_POWER_ACTIVE ? active_power_usage : 0))
// returns true if the area has power on given channel (or doesn't require power).
// defaults to power_channel
/obj/machinery/proc/powered(var/chan = CURRENT_CHANNEL) // defaults to power_channel
if(!src.loc)
return 0
//Don't do this. It allows machines that set use_power to 0 when off (many machines) to
//be turned on again and used after a power failure because they never gain the NOPOWER flag.
//if(!use_power)
// return 1
var/area/A = src.loc.loc // make sure it's in an area
if(!A || !isarea(A))
return 0 // if not, then not powered
if(chan == CURRENT_CHANNEL)
chan = power_channel
return A.powered(chan) // return power status of the area
// called whenever the power settings of the containing area change
// by default, check equipment channel & set/clear NOPOWER flag
// Returns TRUE if NOPOWER stat flag changed.
// can override if needed
/obj/machinery/proc/power_change()
var/oldstat = stat
if(powered(power_channel))
stat &= ~NOPOWER
else
stat |= NOPOWER
. = (stat != oldstat)
return
// Get the amount of power this machine will consume each cycle. Override by experts only!
/obj/machinery/proc/get_power_usage()
return POWER_CONSUMPTION
// DEPRECATED! - USE use_power_oneoff() instead!
/obj/machinery/proc/use_power(var/amount, var/chan = -1) // defaults to power_channel
return src.use_power_oneoff(amount, chan);
// This will have this machine have its area eat this much power next tick, and not afterwards. Do not use for continued power draw.
// Returns actual amount drawn (In theory this could be less than the amount asked for. In pratice it won't be FOR NOW)
/obj/machinery/proc/use_power_oneoff(var/amount, var/chan = CURRENT_CHANNEL)
var/area/A = get_area(src) // make sure it's in an area
if(!A || !isarea(A))
return
if(chan == CURRENT_CHANNEL)
chan = power_channel
A.use_power_oneoff(amount, chan)
return amount // TODO - Return A.use_power_oneoff()'s retval once it returns something.
// Check if we CAN use a given amount of extra power as a one off. Returns amount we could use without actually using it.
// For backwards compatibilty this returns true if the channel is powered. This is consistant with pre-static-power
// behavior of APC powerd machines, but at some point we might want to make this a bit cooler.
/obj/machinery/proc/can_use_power_oneoff(var/amount, var/chan = CURRENT_CHANNEL)
if(powered(chan))
return amount // If channel is powered then you can do it.
return 0
// Do not do power stuff in New/Initialize until after ..()
/obj/machinery/Initialize()
. = ..()
var/power = POWER_CONSUMPTION
REPORT_POWER_CONSUMPTION_CHANGE(0, power)
power_init_complete = TRUE
// Or in Destroy at all, but especially after the ..().
/obj/machinery/Destroy()
if(ismovable(loc))
GLOB.moved_event.unregister(loc, src, .proc/update_power_on_move) // Unregister just in case
var/power = POWER_CONSUMPTION
REPORT_POWER_CONSUMPTION_CHANGE(power, 0)
. = ..()
// Registering moved_event observers for all machines is too expensive. Instead we do it ourselves.
// 99% of machines are always on a turf anyway, very few need recursive move handling.
/obj/machinery/Move()
var/old_loc = loc
if((. = ..()))
update_power_on_move(src, old_loc, loc)
if(ismovable(loc)) // Register for recursive movement (if the thing we're inside moves)
GLOB.moved_event.register(loc, src, .proc/update_power_on_move)
if(ismovable(old_loc)) // Unregister recursive movement.
GLOB.moved_event.unregister(old_loc, src, .proc/update_power_on_move)
/obj/machinery/forceMove(atom/destination)
var/old_loc = loc
if((. = ..()))
update_power_on_move(src, old_loc, loc)
if(ismovable(loc)) // Register for recursive movement (if the thing we're inside moves)
GLOB.moved_event.register(loc, src, .proc/update_power_on_move)
if(ismovable(old_loc)) // Unregister recursive movement.
GLOB.moved_event.unregister(old_loc, src, .proc/update_power_on_move)
/obj/machinery/proc/update_power_on_move(atom/movable/mover, atom/old_loc, atom/new_loc)
var/area/old_area = get_area(old_loc)
var/area/new_area = get_area(new_loc)
if(old_area != new_area)
area_changed(old_area, new_area)
/obj/machinery/proc/area_changed(area/old_area, area/new_area)
if(old_area == new_area || !power_init_complete)
return
var/power = POWER_CONSUMPTION
if(!power)
return // This is the most likely case anyway.
if(old_area)
old_area.power_use_change(power, 0, power_channel) // Remove our usage from old area
if(new_area)
new_area.power_use_change(0, power, power_channel) // Add our usage to new area
power_change() // Force check in case the old area was powered and the new one isn't or vice versa.
//
// Usage Update Procs - These procs are the only allowed way to modify these four variables:
// - use_power, idle_power_usage, active_power_usage, power_channel
//
// Sets the use_power var and then forces an area power update
/obj/machinery/proc/update_use_power(var/new_use_power)
if(use_power == new_use_power)
return
if(!power_init_complete)
use_power = new_use_power
return TRUE // We'll be retallying anyway.
var/old_power = POWER_CONSUMPTION
use_power = new_use_power
var/new_power = POWER_CONSUMPTION
REPORT_POWER_CONSUMPTION_CHANGE(old_power, new_power)
return TRUE
// Sets the power_channel var and then forces an area power update.
/obj/machinery/proc/update_power_channel(var/new_channel)
if(power_channel == new_channel)
return
if(!power_init_complete)
power_channel = new_channel
return TRUE // We'll be retallying anyway.
var/power = POWER_CONSUMPTION
REPORT_POWER_CONSUMPTION_CHANGE(power, 0) // Subtract from old channel
power_channel = new_channel
REPORT_POWER_CONSUMPTION_CHANGE(0, power) // Add to new channel
return TRUE
// Sets the idle_power_usage var and then forces an area power update if use_power was USE_POWER_IDLE
/obj/machinery/proc/update_idle_power_usage(var/new_power_usage)
if(idle_power_usage == new_power_usage)
return
var/old_power = idle_power_usage
idle_power_usage = new_power_usage
if(power_init_complete && use_power == USE_POWER_IDLE) // If this is the channel in use
REPORT_POWER_CONSUMPTION_CHANGE(old_power, new_power_usage)
// Sets the active_power_usage var and then forces an area power update if use_power was USE_POWER_ACTIVE
/obj/machinery/proc/update_active_power_usage(var/new_power_usage)
if(active_power_usage == new_power_usage)
return
var/old_power = active_power_usage
active_power_usage = new_power_usage
if(power_init_complete && use_power == USE_POWER_ACTIVE) // If this is the channel in use
REPORT_POWER_CONSUMPTION_CHANGE(old_power, new_power_usage)
#undef REPORT_POWER_CONSUMPTION_CHANGE
#undef POWER_CONSUMPTION

View File

@@ -54,6 +54,9 @@
recharge_amount = cell.give(recharge_amount)
use_power(recharge_amount / CELLRATE)
else
// Since external power is offline, draw operating current from the internal cell
cell.use(get_power_usage() * CELLRATE)
if(icon_update_tick >= 10)
icon_update_tick = 0
@@ -63,19 +66,6 @@
if(occupant || recharge_amount)
update_icon()
//since the recharge station can still be on even with NOPOWER. Instead it draws from the internal cell.
/obj/machinery/recharge_station/auto_use_power()
if(!(stat & NOPOWER))
return ..()
if(!has_cell_power())
return 0
if(use_power == USE_POWER_IDLE)
cell.use(idle_power_usage * CELLRATE)
else if(use_power >= USE_POWER_ACTIVE)
cell.use(active_power_usage * CELLRATE)
return 1
//Processes the occupant, drawing from the internal power cell if needed.
/obj/machinery/recharge_station/proc/process_occupant()
if(isrobot(occupant))

View File

@@ -111,5 +111,5 @@
if(pow_chan)
var/delta = min(12, ER.chassis.cell.maxcharge-cur_charge)
ER.chassis.give_power(delta)
A.use_power(delta*ER.coeff, pow_chan)
A.use_power_oneoff(delta*ER.coeff, pow_chan)
return

View File

@@ -56,7 +56,7 @@
return
if(!A.powered(EQUIP))
return
A.use_power(EQUIP, 5000)
A.use_power_oneoff(5000, EQUIP)
var/light = A.power_light
A.updateicon()

View File

@@ -160,7 +160,7 @@
var/area/A = get_area(src)
if(A)
if(A.powered(EQUIP) && assembly.give_power(power_amount))
A.use_power(power_amount, EQUIP)
A.use_power_oneoff(power_amount, EQUIP)
// give_power() handles CELLRATE on its own.
// Interacts with the powernet.

View File

@@ -38,7 +38,7 @@
power_usage += tesla_link.passive_charging_rate
battery_module.battery.give(tesla_link.passive_charging_rate * CELLRATE)
A.use_power(power_usage, EQUIP)
A.use_power_oneoff(power_usage, EQUIP)
return TRUE
// Handles power-related things, such as battery interaction, recharging, shutdown when it's discharged

View File

@@ -12,10 +12,6 @@
// Power
//
// This will have this machine have its area eat this much power next tick, and not afterwards. Do not use for continued power draw.
/obj/machinery/proc/use_power_oneoff(var/amount, var/chan = -1)
return use_power(amount, chan)
// Change one of the power consumption vars
/obj/machinery/proc/change_power_consumption(new_power_consumption, use_power_mode = USE_POWER_IDLE)
switch(use_power_mode)

View File

@@ -34,10 +34,10 @@
// controls power to devices in that area
// may be opened to change power cell
// three different channels (lighting/equipment/environ) - may each be set to on, off, or auto
#define POWERCHAN_OFF 0
#define POWERCHAN_OFF_AUTO 1
#define POWERCHAN_ON 2
#define POWERCHAN_ON_AUTO 3
#define POWERCHAN_OFF 0 // Power channel is off and will stay that way dammit
#define POWERCHAN_OFF_AUTO 1 // Power channel is off until power rises above a threshold
#define POWERCHAN_ON 2 // Power channel is on until there is no power
#define POWERCHAN_ON_AUTO 3 // Power channel is on until power drops below a threshold
#define NIGHTSHIFT_AUTO 1
#define NIGHTSHIFT_NEVER 2
@@ -1057,10 +1057,9 @@
return 0
/obj/machinery/power/apc/process()
if(stat & (BROKEN|MAINT))
return
if(!area.requires_power)
return PROCESS_KILL
if(stat & (BROKEN|MAINT))
return
if(failure_timer)
update()
@@ -1069,9 +1068,9 @@
force_update = 1
return
lastused_light = area.usage(LIGHT)
lastused_equip = area.usage(EQUIP)
lastused_environ = area.usage(ENVIRON)
lastused_light = area.usage(LIGHT, lighting >= POWERCHAN_ON)
lastused_equip = area.usage(EQUIP, equipment >= POWERCHAN_ON)
lastused_environ = area.usage(ENVIRON, environ >= POWERCHAN_ON)
area.clear_usage()
lastused_total = lastused_light + lastused_equip + lastused_environ

View File

@@ -64,44 +64,6 @@
/obj/machinery/power/proc/disconnect_terminal(var/obj/machinery/power/terminal/term) // machines without a terminal will just return, no harm no fowl.
return
// returns true if the area has power on given channel (or doesn't require power).
// defaults to power_channel
/obj/machinery/proc/powered(var/chan = -1) // defaults to power_channel
if(!src.loc)
return 0
//Don't do this. It allows machines that set use_power to 0 when off (many machines) to
//be turned on again and used after a power failure because they never gain the NOPOWER flag.
//if(!use_power)
// return 1
var/area/A = src.loc.loc // make sure it's in an area
if(!A || !isarea(A))
return 0 // if not, then not powered
if(chan == -1)
chan = power_channel
return A.powered(chan) // return power status of the area
// increment the power usage stats for an area
/obj/machinery/proc/use_power(var/amount, var/chan = -1) // defaults to power_channel
var/area/A = get_area(src) // make sure it's in an area
if(!A || !isarea(A))
return
if(chan == -1)
chan = power_channel
A.use_power(amount, chan)
/obj/machinery/proc/power_change() // called whenever the power settings of the containing area change
// by default, check equipment channel & set flag
// can override if needed
if(powered(power_channel))
stat &= ~NOPOWER
else
stat |= NOPOWER
return
// connect the machine to a powernet if a node cable is present on the turf
/obj/machinery/power/proc/connect_to_network()
var/turf/T = src.loc
@@ -372,7 +334,7 @@
var/drained_energy = drained_hp*20
if (source_area)
source_area.use_power(drained_energy/CELLRATE)
source_area.use_power_oneoff(drained_energy/CELLRATE, EQUIP)
else if (istype(power_source,/datum/powernet))
var/drained_power = drained_energy/CELLRATE
drained_power = PN.draw_power(drained_power)

View File

@@ -267,7 +267,7 @@
return
if(chan == -1)
chan = power_channel
A.use_power(amount, chan)
A.use_power_oneoff(amount, chan)
/obj/machinery/portable_atmospherics/powered/reagent_distillery/process()
..()

View File

@@ -690,6 +690,7 @@
#include "code\game\machinery\jukebox.dm"
#include "code\game\machinery\lightswitch.dm"
#include "code\game\machinery\machinery.dm"
#include "code\game\machinery\machinery_power.dm"
#include "code\game\machinery\magnet.dm"
#include "code\game\machinery\mass_driver.dm"
#include "code\game\machinery\navbeacon.dm"