From 3f6c1ff622bd7aa22756640218a9c4debab105a9 Mon Sep 17 00:00:00 2001 From: mwerezak Date: Sat, 26 Jul 2014 13:08:02 -0400 Subject: [PATCH] Cleanup, adds setup parameters for atmos machinery Allows atmos machinery efficiency to be adjusted in setup.dm Limits flow rates when moving gas from a turf to avoid very high pressures being created when they shouldn't be. Attempts to limit processing when there isn't much gas to be moved, for performance. Reverts all changes to _gas_mixture.dm --- .../components/binary_devices/pump.dm | 89 ++++++----- .../components/unary/vent_pump.dm | 45 +++--- .../components/unary/vent_scrubber.dm | 143 ++++++++++-------- code/ZAS/_gas_mixture.dm | 79 +--------- code/setup.dm | 18 +++ 5 files changed, 184 insertions(+), 190 deletions(-) diff --git a/code/ATMOSPHERICS/components/binary_devices/pump.dm b/code/ATMOSPHERICS/components/binary_devices/pump.dm index f73c4f0bc81..6534729fc5e 100644 --- a/code/ATMOSPHERICS/components/binary_devices/pump.dm +++ b/code/ATMOSPHERICS/components/binary_devices/pump.dm @@ -67,58 +67,67 @@ Thus, the two variables affect pump operation are set in New(): last_flow_rate = 0 return - var/datum/gas_mixture/source = air1 - var/datum/gas_mixture/sink = air2 - - var/pressure_delta = target_pressure - sink.return_pressure() - - //Calculate necessary moles to transfer using PV=nRT - if(pressure_delta > 0.01 && (source.total_moles > 0) && (source.temperature > 0 || sink.temperature > 0)) - - var/transfer_moles = source.total_moles - /* TODO Uncomment this once we have a good way to get the volume of a pipe network. - //Figure out how much gas to transfer to meet the target pressure. - var/air_temperature = (sink.temperature > 0)? sink.temperature : source.temperature - - var/output_volume = sink.volume * sink.group_multiplier - - //Return the number of moles that would have to be transfered to bring sink to the target pressure - var/transfer_moles = pressure_delta*output_volume/(air_temperature * R_IDEAL_GAS_EQUATION) - */ - - //Calculate the amount of energy required and limit transfer_moles based on available power - var/specific_power = calculate_specific_power(source, sink) //this has to be calculated before we modify any gas mixtures - if (specific_power > 0) - transfer_moles = min(transfer_moles, active_power_usage / specific_power) - - var/power_draw = specific_power*transfer_moles - - var/datum/gas_mixture/removed = source.remove(transfer_moles) - last_flow_rate = (removed.total_moles/(removed.total_moles + source.total_moles))*source.volume - - if (power_draw > 0) - removed.add_thermal_energy(power_draw) //1st law - energy is conserved - handle_pump_power_draw(power_draw) - last_power_draw = power_draw - else - handle_pump_power_draw(idle_power_usage) - last_power_draw = idle_power_usage - - sink.merge(removed) + var/power_draw = -1 + if (air1.temperature > 0 || air2.temperature > 0) + power_draw = pump_gas(air1, air2) if(network1) network1.update = 1 if(network2) network2.update = 1 - else + + if (power_draw < 0) update_use_power(0) last_power_draw = 0 last_flow_rate = 0 - return 1 + else if (power_draw > 0) + handle_pump_power_draw(power_draw) + last_power_draw = power_draw + else + handle_pump_power_draw(idle_power_usage) + last_power_draw = idle_power_usage return 1 +//pumps gas from source to sink, and returns the power used, or -1 if no pumping was done +/obj/machinery/atmospherics/binary/pump/proc/pump_gas(var/datum/gas_mixture/source, var/datum/gas_mixture/sink) + var/pressure_delta = target_pressure - sink.return_pressure() + + if(pressure_delta < 0.01 || source.total_moles < MINUMUM_MOLES_TO_PUMP) + return -1 + + var/transfer_moles = source.total_moles + /* TODO Uncomment this once we have a good way to get the volume of a pipe network. + //Figure out how much gas to transfer to meet the target pressure. + var/air_temperature = (sink.temperature > 0)? sink.temperature : source.temperature + + var/output_volume = sink.volume * sink.group_multiplier + + //Return the number of moles that would have to be transfered to bring sink to the target pressure + var/transfer_moles = pressure_delta*output_volume/(air_temperature * R_IDEAL_GAS_EQUATION) + */ + + //Calculate the amount of energy required and limit transfer_moles based on available power + var/specific_power = calculate_specific_power(source, sink)/ATMOS_PUMP_EFFICIENCY //this has to be calculated before we modify any gas mixtures + if (specific_power > 0) + transfer_moles = min(transfer_moles, active_power_usage / specific_power) + + if (transfer_moles < MINUMUM_MOLES_TO_PUMP) + return -1 + + var/power_draw = specific_power*transfer_moles + + var/datum/gas_mixture/removed = source.remove(transfer_moles) + last_flow_rate = (removed.total_moles/(removed.total_moles + source.total_moles))*source.volume + + if (power_draw > 0) + removed.add_thermal_energy(power_draw) //1st law - energy is conserved + + sink.merge(removed) + + return power_draw + //Radio remote control /obj/machinery/atmospherics/binary/pump/proc/set_frequency(new_frequency) diff --git a/code/ATMOSPHERICS/components/unary/vent_pump.dm b/code/ATMOSPHERICS/components/unary/vent_pump.dm index 6e76ddcbd01..eee3eef0daa 100644 --- a/code/ATMOSPHERICS/components/unary/vent_pump.dm +++ b/code/ATMOSPHERICS/components/unary/vent_pump.dm @@ -144,16 +144,14 @@ last_power_draw = 0 last_flow_rate = 0 return 0 - + var/datum/gas_mixture/environment = loc.return_air() var/environment_pressure = environment.return_pressure() - - if(air_contents.temperature == 0 && environment.temperature == 0) - return 0 var/pressure_delta = DEFAULT_PRESSURE_DELTA - if(pressure_delta > 0.5) + var/power_draw = -1 + if((air_contents.temperature > 0 || environment.temperature > 0) && pressure_delta > 0.5) if(pump_direction) //internal -> external if(pressure_checks & PRESSURE_CHECK_EXTERNAL) pressure_delta = min(pressure_delta, external_pressure_bound - environment_pressure) //increasing the pressure here @@ -166,7 +164,7 @@ var/air_temperature = environment.temperature? environment.volume : air_contents.temperature var/transfer_moles = pressure_delta*output_volume/(air_temperature * R_IDEAL_GAS_EQUATION) - transfer_gas(air_contents, environment, transfer_moles) + power_draw = transfer_gas(air_contents, environment, transfer_moles) else //external -> internal if(pressure_checks & PRESSURE_CHECK_EXTERNAL) pressure_delta = min(pressure_delta, environment_pressure - external_pressure_bound) //decreasing the pressure here @@ -178,46 +176,57 @@ var/air_temperature = air_contents.temperature? air_contents.temperature : environment.temperature var/transfer_moles = pressure_delta*output_volume/(air_temperature * R_IDEAL_GAS_EQUATION) - transfer_gas(environment, air_contents, transfer_moles) + //limit flow rate from turfs + transfer_moles = min(transfer_moles, environment.total_moles*MAX_SIPHON_FLOWRATE/environment.volume) //group_multiplier gets divided out here + + power_draw = transfer_gas(environment, air_contents, transfer_moles) if(network) network.update = 1 - else + + if (power_draw < 0) last_power_draw = 0 last_flow_rate = 0 update_use_power(0) + if (power_draw > 0) + handle_pump_power_draw(power_draw) + last_power_draw = power_draw + else + handle_pump_power_draw(idle_power_usage) + last_power_draw = idle_power_usage + return 1 +//pumps gas from source to sink and returns the power used, or -1 if no pumping was done. /obj/machinery/atmospherics/unary/vent_pump/proc/transfer_gas(datum/gas_mixture/source, datum/gas_mixture/sink, var/transfer_moles) - if(source.total_moles == 0) - update_use_power(0) - return + if(source.total_moles < MINUMUM_MOLES_TO_PUMP) + return -1 //limit transfer_moles by available power - var/specific_power = calculate_specific_power(source, sink) //this has to be calculated before we modify any gas mixtures + var/specific_power = calculate_specific_power(source, sink)/ATMOS_PUMP_EFFICIENCY //this has to be calculated before we modify any gas mixtures if (specific_power > 0) transfer_moles = min(transfer_moles, active_power_usage / specific_power) //Get the gas to be transferred + if (transfer_moles < MINUMUM_MOLES_TO_PUMP) + return -1 //don't bother + var/datum/gas_mixture/removed = source.remove(transfer_moles) if (isnull(removed)) //not sure why this would happen, but it does at the very beginning of the game - return + return -1 last_flow_rate = (removed.total_moles/(removed.total_moles + source.total_moles))*source.volume var/power_draw = specific_power*transfer_moles if (power_draw > 0) removed.add_thermal_energy(power_draw) - handle_pump_power_draw(power_draw) - last_power_draw = power_draw - else - handle_pump_power_draw(idle_power_usage) - last_power_draw = idle_power_usage //merge the removed gas into the sink sink.merge(removed) + + return power_draw //Radio remote control diff --git a/code/ATMOSPHERICS/components/unary/vent_scrubber.dm b/code/ATMOSPHERICS/components/unary/vent_scrubber.dm index ebdea2fc98c..99dd0286b66 100644 --- a/code/ATMOSPHERICS/components/unary/vent_scrubber.dm +++ b/code/ATMOSPHERICS/components/unary/vent_scrubber.dm @@ -122,66 +122,17 @@ return 0 var/datum/gas_mixture/environment = loc.return_air() - if ((environment.total_moles == 0) || (environment.temperature == 0 && air_contents.temperature == 0)) + + var/power_draw = -1 + if (environment.temperature > 0 || air_contents.temperature > 0) + if(scrubbing) + power_draw = filter_gas(environment) + else //Just siphon all air + power_draw = siphon_gas(environment) + + if (power_draw < 0) update_use_power(0) - return - - var/power_draw - if(scrubbing) - //Filter it - var/total_specific_power = 0 //the power required to remove one mole of filterable gas - var/total_filterable_moles = 0 - var/list/specific_power_gas = list() - for (var/g in scrubbing_gas) - if (environment.gas[g] < 0.1) - continue //don't bother - - var/specific_power = calculate_specific_power_gas(g, environment, air_contents) - specific_power_gas[g] = specific_power - total_specific_power += specific_power - total_filterable_moles += environment.gas[g] - - if (total_filterable_moles == 0) - update_use_power(0) - return - - //Calculate the amount of energy required and limit transfer_moles based on available power - power_draw = 0 - var/total_transfer_moles = total_filterable_moles - if (total_specific_power > 0) - total_transfer_moles = min(total_transfer_moles, active_power_usage/total_specific_power) - - for (var/g in scrubbing_gas) - var/transfer_moles = environment.gas[g] - if (specific_power_gas[g] > 0) - //if our flow rate is limited by available power, the proportion of the filtered gas is based on mole ratio - transfer_moles = min(transfer_moles, total_transfer_moles*(environment.gas[g]/total_filterable_moles)) - - environment.gas[g] -= transfer_moles - air_contents.gas[g] += transfer_moles - power_draw += specific_power_gas[g]*transfer_moles - - //Remix the resulting gases - air_contents.update_values() - environment.update_values() - - else //Just siphon all air - var/transfer_moles = environment.total_moles - - //Calculate the amount of energy required - var/specific_power = calculate_specific_power(environment, air_contents) //this has to be calculated before we modify any gas mixtures - if (specific_power > 0) - transfer_moles = min(transfer_moles, active_power_usage / specific_power) - - if (transfer_moles < 0.01) - update_use_power(0) - return //don't bother - - power_draw = specific_power*transfer_moles - air_contents.merge(environment.remove(transfer_moles)) - - if (power_draw > 0) - air_contents.add_thermal_energy(power_draw) + else if (power_draw > 0) //last_power_draw = power_draw handle_pump_power_draw(power_draw) else @@ -193,6 +144,80 @@ return 1 +//filters gas from environment and returns the amount of power used, or -1 if no filtering was done +/obj/machinery/atmospherics/unary/vent_scrubber/proc/filter_gas(datum/gas_mixture/environment) + //Filter it + var/total_specific_power = 0 //the power required to remove one mole of filterable gas + var/total_filterable_moles = 0 + var/list/specific_power_gas = list() + for (var/g in scrubbing_gas) + if (environment.gas[g] < MINUMUM_MOLES_TO_PUMP) + continue //don't bother + + var/specific_power = calculate_specific_power_gas(g, environment, air_contents)/ATMOS_FILTER_EFFICIENCY + specific_power_gas[g] = specific_power + total_specific_power += specific_power + total_filterable_moles += environment.gas[g] + + if (total_filterable_moles < MINUMUM_MOLES_TO_PUMP) + return -1 + + //Figure out how much of each gas to filter + var/total_transfer_moles = total_filterable_moles + + //limit flow rate from turfs + total_transfer_moles = min(total_transfer_moles, environment.total_moles*MAX_FILTER_FLOWRATE/environment.volume) //group_multiplier gets divided out here + + //limit transfer_moles based on available power + var/power_draw = 0 + if (total_specific_power > 0) + total_transfer_moles = min(total_transfer_moles, active_power_usage/total_specific_power) + + for (var/g in scrubbing_gas) + var/transfer_moles = environment.gas[g] + if (specific_power_gas[g] > 0) + //if our flow rate is being limited by available power, the proportion of the filtered gas is based on mole ratio + transfer_moles = min(transfer_moles, total_transfer_moles*(environment.gas[g]/total_filterable_moles)) + + environment.gas[g] -= transfer_moles + air_contents.gas[g] += transfer_moles + power_draw += specific_power_gas[g]*transfer_moles + + if (power_draw > 0) + air_contents.add_thermal_energy(power_draw) + + //Remix the resulting gases + air_contents.update_values() + environment.update_values() + + return power_draw + +//siphons gas from environment and returns the power used, or -1 if no siphoning was done +/obj/machinery/atmospherics/unary/vent_scrubber/proc/siphon_gas(datum/gas_mixture/environment) + if (environment.total_moles < MINUMUM_MOLES_TO_PUMP) + return -1 //no point doing all this processing when source is a vacuum + + var/transfer_moles = environment.total_moles + + //limit flow rate from turfs + transfer_moles = min(transfer_moles, environment.total_moles*MAX_SIPHON_FLOWRATE/environment.volume) //group_multiplier gets divided out here + + //Calculate the amount of energy required and limit transfer_moles based on available power + var/specific_power = calculate_specific_power(environment, air_contents)/ATMOS_PUMP_EFFICIENCY //this has to be calculated before we modify any gas mixtures + if (specific_power > 0) + transfer_moles = min(transfer_moles, active_power_usage / specific_power) + + if (transfer_moles < MINUMUM_MOLES_TO_PUMP) + return -1 //don't bother + + var/power_draw = specific_power*transfer_moles + var/datum/gas_mixture/removed = environment.remove(transfer_moles) + if (power_draw > 0) + removed.add_thermal_energy(power_draw) + air_contents.merge(removed) + + return power_draw + /obj/machinery/atmospherics/unary/vent_scrubber/hide(var/i) //to make the little pipe section invisible, the icon changes. update_icon() diff --git a/code/ZAS/_gas_mixture.dm b/code/ZAS/_gas_mixture.dm index 2c9471c4ee1..2a072e8ef9c 100644 --- a/code/ZAS/_gas_mixture.dm +++ b/code/ZAS/_gas_mixture.dm @@ -4,24 +4,16 @@ What are the archived variables for? This prevents race conditions that arise based on the order of tile processing. */ -#define SPECIFIC_HEAT_TOXIN 200 // J/(mol*K) -#define SPECIFIC_HEAT_AIR 20 // J/(mol*K) -#define SPECIFIC_HEAT_CDO 30 // J/(mol*K) +#define SPECIFIC_HEAT_TOXIN 200 +#define SPECIFIC_HEAT_AIR 20 +#define SPECIFIC_HEAT_CDO 30 #define HEAT_CAPACITY_CALCULATION(oxygen,carbon_dioxide,nitrogen,phoron) \ max(0, carbon_dioxide * SPECIFIC_HEAT_CDO + (oxygen + nitrogen) * SPECIFIC_HEAT_AIR + phoron * SPECIFIC_HEAT_TOXIN) -//we should really have a datum for each gas instead of a bunch of constants -#define MOL_MASS_O2 0.032 // kg/mol -#define MOL_MASS_N2 0.028 // kg/mol -#define MOL_MASS_CDO 0.044 // kg/mol -#define MOL_MASS_PHORON 0.289 // kg/mol - #define MINIMUM_HEAT_CAPACITY 0.0003 #define QUANTIZE(variable) (round(variable,0.0001)) #define TRANSFER_FRACTION 5 //What fraction (1/#) of the air difference to try and transfer -#define SPECIFIC_ENTROPY_VACUUM 1500 //technically vacuum doesn't have a specific entropy. Just use a really big number here to show that it's easy to add gas to vacuum and hard to take gas out. - /hook/startup/proc/createGasOverlays() plmaster = new /obj/effect/overlay() plmaster.icon = 'icons/effects/tile_effects.dmi' @@ -36,11 +28,11 @@ What are the archived variables for? slmaster.mouse_opacity = 0 return 1 -/datum/gas/sleeping_agent/specific_heat = 40 //These are used for the "Trace Gases" stuff, but is buggy. //J/(mol*K) +/datum/gas/sleeping_agent/specific_heat = 40 //These are used for the "Trace Gases" stuff, but is buggy. -/datum/gas/oxygen_agent_b/specific_heat = 300 //J/(mol*K) +/datum/gas/oxygen_agent_b/specific_heat = 300 -/datum/gas/volatile_fuel/specific_heat = 30 //J/(mol*K) +/datum/gas/volatile_fuel/specific_heat = 30 /datum/gas var/moles = 0 @@ -149,65 +141,6 @@ What are the archived variables for? return max(MINIMUM_HEAT_CAPACITY,heat_capacity_archived) -/datum/gas_mixture/proc/add_thermal_energy(var/thermal_energy) - //Purpose: Adjusting temperature based on thermal energy transfer - //Called by: Anyone who wants to add or remove energy from the gas mix - //Inputs: An amount of energy in J to be added. Negative values remove energy. - //Outputs: The actual thermal energy change. Only relevant if you are removing energy. - - var/old_temperature = temperature - var/heat_capacity = heat_capacity() - - temperature += thermal_energy/heat_capacity - if (temperature < TCMB) - temperature = TCMB - - return (temperature - old_temperature)*heat_capacity - -/datum/gas_mixture/proc/get_thermal_energy_change(var/new_temperature) - //Purpose: Determining how much thermal energy is required - //Called by: Anyone. Machines that want to adjust the temperature of a gas mix. - //Inputs: None - //Outputs: The amount of energy required to get to the new temperature in J. A negative value means that energy needs to be removed. - - return heat_capacity()*(new_temperature - temperature) - - -//This is so overkill for spessmen it's hilarious. -//While this proc will return an accurate measure of the entropy, it's much easier to use the specific_entropy_change() proc. -/datum/gas_mixture/proc/specific_entropy() - //Purpose: Returning the specific entropy of the gas mix, i.e. the entropy gained or lost per mole of gas added or removed. - //Called by: Anyone who wants to know how much energy it takes to move gases around in a steady state process (e.g. gas pumps) - //Inputs: None - //Outputs: Specific Entropy. - - //Jut assume everything is an ideal gas, so we can use the Ideal Gas Sackur-Tetrode equation. - //After we convert to moles and Liters and extract all those crazy constants inside the ln() we end up with: - //S = R * moles * ( ln[ constant * volume / moles * (molecular_mass * internal_energy / moles)^(3/2) ] + 5/2 ) - //Where constant is IDEAL_GAS_ENTROPY_CONSTANT defined in setup.dm - - //We need to do this calculation for each type of gas in the mix and add them all up, to properly capture the entropy of mixing. - //It would be nice if each gas type was a datum, then we could just iterate through a list - - //the number of moles inside the square root gets divided out - var/sp_entropy_oxygen = ( log( IDEAL_GAS_ENTROPY_CONSTANT * volume / (oxygen + 0.001) * sqrt( ( MOL_MASS_O2 * SPECIFIC_HEAT_AIR * (temperature + 1) ) ** 3 ) + 1) + 5/2 ) - - var/sp_entropy_nitrogen = ( log( IDEAL_GAS_ENTROPY_CONSTANT * volume / (nitrogen + 0.001) * sqrt( ( MOL_MASS_N2 * SPECIFIC_HEAT_AIR * (temperature + 1) ) ** 3 ) + 1 ) + 5/2 ) - - var/sp_entropy_carbon_dioxide = ( log( IDEAL_GAS_ENTROPY_CONSTANT * volume / (carbon_dioxide + 0.001) * sqrt( ( MOL_MASS_CDO * SPECIFIC_HEAT_CDO * (temperature + 1) + 1 ) ** 3 ) ) + 5/2 ) - - var/sp_entropy_phoron = ( log( IDEAL_GAS_ENTROPY_CONSTANT * volume / (phoron + 0.001) * sqrt( ( MOL_MASS_PHORON * SPECIFIC_HEAT_TOXIN * (temperature + 1) ) ** 3 ) + 1 ) + 5/2 ) - - if (total_moles > 0) - var/oxygen_ratio = oxygen/total_moles - var/nitrogen_ratio = nitrogen/total_moles - var/carbon_dioxide_ratio = carbon_dioxide/total_moles - var/phoron_ratio = phoron/total_moles - - return R_IDEAL_GAS_EQUATION * ( oxygen_ratio*sp_entropy_oxygen + nitrogen_ratio*sp_entropy_nitrogen + carbon_dioxide_ratio*sp_entropy_carbon_dioxide + phoron_ratio*sp_entropy_phoron ) - - return SPECIFIC_ENTROPY_VACUUM - /datum/gas_mixture/proc/total_moles() return total_moles /*var/moles = oxygen + carbon_dioxide + nitrogen + phoron diff --git a/code/setup.dm b/code/setup.dm index 71acad9a998..552386c70b0 100644 --- a/code/setup.dm +++ b/code/setup.dm @@ -823,3 +823,21 @@ var/list/RESTRICTED_CAMERA_NETWORKS = list( //Those networks can only be accesse #define IS_UNATHI 4 #define MAX_GEAR_COST 5 //Used in chargen for loadout limit. + + +/* + Atmos Machinery +*/ + +//These limit the flow rate when taking gas away from turfs. This is necessary because moving 2500 L in a single iteration causes a huge amount of error with pressure and gas calculations. +//This error is related to the fact that we do everything in 1 second steps instead of continuous time. Limiting the flow rate will prevent pressures from overshooting too much. +#define MAX_SIPHON_FLOWRATE 500 //L/s +#define MAX_FILTER_FLOWRATE 200 //L/s + +//These control how easy or hard it is to create huge pressure gradients with pumps and filters. Lower values means it takes longer to create large pressures differences. +//If you want to limit the ability of players to create very high pressures then it makes more sense to adjust these instead of artificially limiting the pump settings. +//Has no effect on pumping gasses from high pressure to low, only from low to high. Must be between 0 and 1. +#define ATMOS_PUMP_EFFICIENCY 1.0 +#define ATMOS_FILTER_EFFICIENCY 0.9 + +#define MINUMUM_MOLES_TO_PUMP 0.01 //will not bother pumping or filtering if the gas source as fewer than this amount of moles, to help with performance. \ No newline at end of file