mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-23 05:00:55 +01:00
## About The Pull Request ### Summary This PR changes internal structure of `/datum/gas_mixture`: `gases[gas_id][MOLES]` refactored into `moles[gas_id]`, `gases[gas_id][ARCHIVE]` into `moles_archive[gas_id]` and `gases[gas_id][GAS_META]` into `gas_meta` static variable. This allows us to use BYOND 516 vector functions for calculating total moles and heat capacity. Also it simplifies some parts of the code, allowing us to get rid of macros `ADD_GAS` and `ASSERT_GAS`. According to the profiler `/turf/open/process_cell` time is reduced by ~20%. ### Details `gas_mixture.gases` was a nested 2d-list with MOLES, ARCHIVE and GAS_META for each gas_id. For example, to get gas moles you had to do `gases[gas_id][MOLES]`. I've changed this structure to be as follows: `moles[gas_id]` - moles for the gas, `moles_archive[gas_id]` - archived version of moles, `gas_meta[KEY][gas_id]` - static var with meta information for the gas. Since I removed key GAS_META from the gases, `gas_meta` was moved to the static variable and the order of keys in the array was changed from `gas_meta[gas_id][META_KEY]' to 'gas_meta[META_KEY][gas_id]`. This was done to allow using it in vector calculations (for example heat capacity or fusion power). Static variable access is very fast and it is considered as accessing a global in the bytecode. Byond 516 introduced new vector functions: `values_sum`, `values_dot` and others. These functions are very fast, but operate only on associative lists. This allows us to change the way we calculate total_moles and heat_capacity - very hot and heavily used functions. `total_moles()` became just `values_sum(moles)`, and `heat_capacity` is just a dot product: `values_dot(moles, gas_meta[META_GAS_SPECIFIC_HEAT])`. As a side bonus, since `moles` is just an associative list, you don't really need old macros `ADD_GAS` and `ASSERT_GAS` - all they did was to make a copy of a list[3] with default value [0, 0, gas_meta] for specific gas. Now when you're adding gas you can just use `moles[gas_id] += amount` and when you query amount of gas you can just query the key (for example `moles[/datum/gas/oxygen]`) if the key does not exist, it returns null and works as 0 for all arithmetic and logic operations. For example, old code would be `if (!air.gases[/datum/gas/oxygen] || air.gases[/datum/gas/oxygen][MOLES] < 1)` and now it is `if (moles[/datum/gas/oxygen] < 1)`. This simplifies some parts of the code and also speeds things up. For the performance comparison I used Tracy profiler. I've done many different tests, and they all show slightly different numbers, but overall speedup for `process_cell` is about 20%. (-20% to average time per call from ). My testing setup was as follows: Load Icebox, drop 30/60/90 radius bomb in the middle of the bridge, set code to blue, wait 10 minutes until the round ends. Also I fixed random seed in the master controller and in the planetary gas randomization so generated maps are the same between tests. Althought it's not very realistic, it generates a lot of samples for the `process_cell` (around ~3.5M per 10 minutes). Another test I did was a plasmafire in an 8x8 space, on runtime station, it showed (-24% time on process_cell). Another test was a emagged holodeck burn test, it showed (-13% time) As for other functions of gas_mixture: `total_moles`: -50%(2x speedup), `heat_capacity`: -65%(3x speedup), `share`: -30%, `react`: -20%. Timings of all those functions is in microseconds range and they are very hot (call count is in the same order as process_cell) <details><summary>Some pictures from profiler</summary> <img width="569" height="642" alt="process_cell" src="https://github.com/user-attachments/assets/76fa0c27-719d-485d-9bfc-859fef788999" /> <img width="572" height="315" alt="image" src="https://github.com/user-attachments/assets/f68497e9-4db8-4a9c-b43f-ad04e6dc5cac" /> <img width="569" height="317" alt="image" src="https://github.com/user-attachments/assets/d7249e7b-f344-47a6-8b39-1bab0521182d" /> <img width="541" height="316" alt="image" src="https://github.com/user-attachments/assets/1bef71fd-533d-40fa-a85e-7a803ad322f7" /> <img width="519" height="409" alt="image" src="https://github.com/user-attachments/assets/51165289-174e-403d-a09c-787dd9af136a" /> <img width="523" height="318" alt="image" src="https://github.com/user-attachments/assets/fe4b1db0-17d8-47f9-8fd0-e2ecef2ee66a" /> </details> <details><summary>Setting up a profiler</summary> If you wanna to reproduce my results here is a list of steps 1. download: https://github.com/goonstation/byond-tracy-writer (this one has offsets for my version 1677) 2. build the dll, drop in the tgstation/ folder 3. download rtracy https://github.com/Dimach/rtracy 4. download Tracy profiler (0.13.1) https://github.com/wolfpld/tracy 5. uncomment `#define USE_BYOND_TRACY` in `_compile_options.dm` 6. build tgstation 7. open dream daemon, run the desired test, after round end dream daemon closes 8. navigate to tgstation/data/profiler, find the `123412341234.utracy` file 9. run `rtracty 123412341234.utracy` 10. open tracy-profiler.exe, press Connect, save the profiler data 11. repeat steps 5-10 with another branch, save another profiler data 12. open tracy-profiler, open first data, press compare, open second data </details> ## Why It's Good For The Game ## Changelog 🆑 refactor: Atmos refactor & speedup by utilizing BYOND 516 vector functions /🆑 --------- Co-authored-by: san7890 <the@san7890.com>
146 lines
5.5 KiB
Plaintext
146 lines
5.5 KiB
Plaintext
/datum/mutation/olfaction
|
|
name = "Transcendent Olfaction"
|
|
desc = "Your sense of smell is comparable to that of a canine."
|
|
quality = POSITIVE
|
|
difficulty = 12
|
|
text_gain_indication = span_notice("Smells begin to make more sense...")
|
|
text_lose_indication = span_notice("Your sense of smell goes back to normal.")
|
|
power_path = /datum/action/cooldown/spell/olfaction
|
|
instability = POSITIVE_INSTABILITY_MODERATE
|
|
synchronizer_coeff = 1
|
|
|
|
/datum/mutation/olfaction/setup()
|
|
. = ..()
|
|
var/datum/action/cooldown/spell/olfaction/to_modify = .
|
|
if(!istype(to_modify)) // null or invalid
|
|
return
|
|
|
|
to_modify.sensitivity = GET_MUTATION_SYNCHRONIZER(src)
|
|
|
|
/datum/action/cooldown/spell/olfaction
|
|
name = "Remember the Scent"
|
|
desc = "Get a scent off of the item you're currently holding to track it. \
|
|
With an empty hand, you'll track the scent you've remembered."
|
|
button_icon_state = "nose"
|
|
|
|
cooldown_time = 10 SECONDS
|
|
spell_requirements = NONE
|
|
|
|
/// Weakref to the mob we're tracking
|
|
var/datum/weakref/tracking_ref
|
|
/// Our nose's sensitivity
|
|
var/sensitivity = 1
|
|
|
|
/datum/action/cooldown/spell/olfaction/is_valid_target(atom/cast_on)
|
|
if(!isliving(cast_on))
|
|
return FALSE
|
|
|
|
var/mob/living/living_cast_on = cast_on
|
|
if(ishuman(living_cast_on) && !living_cast_on.get_bodypart(BODY_ZONE_HEAD))
|
|
to_chat(owner, span_warning("You have no nose!"))
|
|
return FALSE
|
|
|
|
if(HAS_TRAIT(living_cast_on, TRAIT_ANOSMIA)) //Anosmia quirk holders can't smell anything
|
|
to_chat(owner, span_warning("You can't smell!"))
|
|
return FALSE
|
|
|
|
return TRUE
|
|
|
|
/datum/action/cooldown/spell/olfaction/cast(mob/living/cast_on)
|
|
. = ..()
|
|
// Can we sniff? is there miasma in the air?
|
|
var/datum/gas_mixture/air = cast_on.loc.return_air()
|
|
|
|
if(air.moles[/datum/gas/miasma])
|
|
cast_on.adjust_disgust(sensitivity * 45)
|
|
to_chat(cast_on, span_warning("With your overly sensitive nose, \
|
|
you get a whiff of stench and feel sick! Try moving to a cleaner area!"))
|
|
return
|
|
|
|
var/atom/sniffed = cast_on.get_active_held_item()
|
|
if(sniffed)
|
|
pick_up_target(cast_on, sniffed)
|
|
else
|
|
follow_target(cast_on)
|
|
|
|
/// Attempt to pick up a new target based on the fingerprints on [sniffed].
|
|
/datum/action/cooldown/spell/olfaction/proc/pick_up_target(mob/living/caster, atom/sniffed)
|
|
var/mob/living/carbon/old_target = tracking_ref?.resolve()
|
|
var/list/possibles = list()
|
|
var/list/prints = GET_ATOM_FINGERPRINTS(sniffed)
|
|
if(prints)
|
|
for(var/mob/living/carbon/to_check as anything in GLOB.carbon_list)
|
|
if(prints[md5(to_check.dna?.unique_identity)])
|
|
possibles |= to_check
|
|
|
|
// There are no finger prints on the atom, so nothing to track
|
|
if(!length(possibles))
|
|
to_chat(caster, span_warning("Despite your best efforts, there are no scents to be found on [sniffed]..."))
|
|
return
|
|
|
|
var/mob/living/carbon/new_target = tgui_input_list(caster, "Scent to remember", "Scent Tracking", sort_names(possibles))
|
|
if(QDELETED(src) || QDELETED(caster))
|
|
return
|
|
|
|
if(QDELETED(new_target))
|
|
// We don't have a new target OR an old target
|
|
if(QDELETED(old_target))
|
|
to_chat(caster, span_warning("You decide against remembering any scents. \
|
|
Instead, you notice your own nose in your peripheral vision. \
|
|
This goes on to remind you of that one time you started breathing manually and couldn't stop. \
|
|
What an awful day that was."))
|
|
tracking_ref = null
|
|
|
|
// We don't have a new target, but we have an old target to fall back on
|
|
else
|
|
to_chat(caster, span_notice("You return to tracking [old_target]. The hunt continues."))
|
|
on_the_trail(caster)
|
|
return
|
|
|
|
// We have a new target to track
|
|
to_chat(caster, span_notice("You pick up the scent of [new_target]. The hunt begins."))
|
|
tracking_ref = WEAKREF(new_target)
|
|
on_the_trail(caster)
|
|
|
|
/// Attempt to follow our current tracking target.
|
|
/datum/action/cooldown/spell/olfaction/proc/follow_target(mob/living/caster)
|
|
var/mob/living/carbon/current_target = tracking_ref?.resolve()
|
|
// Either our weakref failed to resolve (our target's gone),
|
|
// or we never had a target in the first place
|
|
if(QDELETED(current_target))
|
|
to_chat(caster, span_warning("You're not holding anything to smell, \
|
|
and you haven't smelled anything you can track. You smell your skin instead; it's kinda salty."))
|
|
tracking_ref = null
|
|
return
|
|
|
|
on_the_trail(caster)
|
|
|
|
/// Actually go through and give the user a hint of the direction our target is.
|
|
/datum/action/cooldown/spell/olfaction/proc/on_the_trail(mob/living/caster)
|
|
var/mob/living/carbon/current_target = tracking_ref?.resolve()
|
|
//Using get_turf to deal with those pesky closets that put your x y z to 0
|
|
var/turf/current_target_turf = get_turf(current_target)
|
|
var/turf/caster_turf = get_turf(caster)
|
|
if(!current_target)
|
|
to_chat(caster, span_warning("You're not tracking a scent, but the game thought you were. \
|
|
Something's gone wrong! Report this as a bug."))
|
|
stack_trace("[type] - on_the_trail was called when no tracking target was set.")
|
|
tracking_ref = null
|
|
return
|
|
|
|
if(current_target == caster)
|
|
to_chat(caster, span_warning("You smell out the trail to yourself. Yep, it's you."))
|
|
return
|
|
|
|
if(caster_turf.z < current_target_turf.z)
|
|
to_chat(caster, span_warning("The trail leads... way up above you? Huh. They must be really, really far away."))
|
|
return
|
|
|
|
else if(caster_turf.z > current_target_turf.z)
|
|
to_chat(caster, span_warning("The trail leads... way down below you? Huh. They must be really, really far away."))
|
|
return
|
|
|
|
var/direction_text = span_bold("[dir2text(get_dir(caster_turf, current_target_turf))]")
|
|
if(direction_text)
|
|
to_chat(caster, span_notice("You consider [current_target]'s scent. The trail leads [direction_text]."))
|