Maybe improves radio tts somewhat (hypothetically halves the cost but who really knows) (#96799)

## About The Pull Request

Right now for radio TTS we get hearers twice. 


https://github.com/tgstation/tgstation/blob/62eb74613a141f197cca1f274431b734bd064034/code/game/machinery/telecomms/broadcasting.dm#L165-L170

This is not ideal as it's quite an expensive part of radio handling

<img width="765" height="141" alt="image"
src="https://github.com/user-attachments/assets/8245183b-c810-46bf-b466-9879c33e766c"
/>

My thought is we can remove one of the calls by only calling one or the
other
- If TTS is enabled, call `get_hearers_in_radio_ranges_track_radios` and
combine the list of hearers from *that* list
- If TTS is disabled, just use `get_hearers_in_radio_ranges`

```dm
	// Flat list of mobs who can hear the message
	var/list/receive
	// Assoc list of weakref to a radio to list of weakrefs to mobs who can hear the message
	var/list/receive_radios

	if(tts_radio_id) // only do this if we have a TTS identifier to save on perf
		var/list/recieved_radios_raw = get_hearers_in_radio_ranges_track_radios(radios, frequency)
		receive = list()
		receive_radios = list()
		for(var/radio, radio_hearers in recieved_radios_raw)
			receive |= radio_hearers
			var/datum/weakref/radio_ref = WEAKREF(radio)
			for(var/mob/possible_hearer as anything in radio_hearers)
				if(!isnull(possible_hearer.client) && can_hear_radio_tts(possible_hearer, frequency))
					receive_radios[radio_ref] ||= list()
					receive_radios[radio_ref] += WEAKREF(possible_hearer)

	else
		receive = get_hearers_in_radio_ranges(radios)
```

I also went through and cleaned up TTS handling a bit to make it easier
to parse, mostly completing my reviews from
https://github.com/tgstation/tgstation/pull/95369

No I did not test it

## Changelog

🆑 Melbert
code: Cleaned up radio TTS handling a bit, maybe it'll perform better,
report anything weird
/🆑
This commit is contained in:
MrMelbert
2026-07-16 16:03:15 +02:00
committed by GitHub
parent 4fec9f5603
commit 97cb4ae3b5
6 changed files with 154 additions and 120 deletions
+99 -73
View File
@@ -1,3 +1,6 @@
#define TTS_REQUEST_REF "ref"
#define TTS_REQUEST_EXPIRE "expiry_time"
SUBSYSTEM_DEF(tts)
name = "Text To Speech"
wait = 0.05 SECONDS
@@ -276,10 +279,8 @@ SUBSYSTEM_DEF(tts)
var/datum/http_response/radio_blips_response = current_request.request_blips_radio.into_response()
var/datum/http_response/radio_gibberish_response = current_request.request_radio_gibberish.into_response()
if(current_request.requests_errored())
if(queued_radio_messages[identifier])
queued_radio_messages.Remove(identifier)
if(queued_radio_messages_compression[identifier])
queued_radio_messages_compression.Remove(identifier)
queued_radio_messages -= identifier
queued_radio_messages_compression -= identifier
current_request.timed_out = TRUE
log_tts("TTS HTTP request errored | Normal: [normal_response.error] | Blips: [blips_response.error] | Radio: [radio_response.error] | Radio Blips: [radio_blips_response.error] | Radio Gibberish [radio_gibberish_response.error]", list(
"normal" = normal_response,
@@ -299,8 +300,6 @@ SUBSYSTEM_DEF(tts)
current_request.audio_length_blips_radio = text2num(radio_blips_response.headers["audio-length"]) * 10 || 0
if(length(radio_gibberish_response.headers) && radio_gibberish_response.headers.Find("audio-length"))
current_request.audio_length_radio_gibberish = text2num(radio_gibberish_response.headers["audio-length"]) * 10
if(!current_request.audio_length_radio_gibberish)
current_request.audio_length_radio_gibberish = 0
current_request.audio_file = "tmp/tts/[identifier].ogg"
current_request.audio_file_blips = "tmp/tts/[identifier]_blips.ogg" // We aren't as concerned about the audio length for blips as we are with actual speech
current_request.audio_file_radio = "tmp/tts/[identifier]_radio.ogg"
@@ -334,21 +333,17 @@ SUBSYSTEM_DEF(tts)
// For example, if a TTS message plays for more than 7 seconds, which is our current timeout limit,
// then the next TTS message would be unable to play.
var/timeout_start = current_target.when_to_play
if(!timeout_start)
// In the normal case, we just set timeout to start_time as it means we aren't waiting on
// a TTS message to finish playing
timeout_start = current_target.start_time
// In the normal case, we just set timeout to start_time as it means we aren't waiting on
// a TTS message to finish playing
var/timeout_start = current_target.when_to_play || current_target.start_time
var/timeout = timeout_start + message_timeout
// Here, we check if the request has timed out or not.
// If current_target.timed_out is set to TRUE, it means the request failed in some way
// and there is no TTS audio file to play.
if(timeout < world.time || current_target.timed_out)
if(queued_radio_messages[current_target.identifier])
queued_radio_messages.Remove(current_target.identifier)
if(queued_radio_messages_compression[current_target.identifier])
queued_radio_messages_compression.Remove(current_target.identifier)
queued_radio_messages -= current_target.identifier
queued_radio_messages_compression -= current_target.identifier
SHIFT_DATA_ARRAY(queued_tts_messages, tts_target, data)
continue
@@ -367,11 +362,28 @@ SUBSYSTEM_DEF(tts)
audio_file = new(current_target.audio_file)
SEND_SOUND(current_target.target, audio_file)
SHIFT_DATA_ARRAY(queued_tts_messages, tts_target, data)
else if(current_target.when_to_play < world.time)
audio_file = new(current_target.audio_file)
audio_file_blips = new(current_target.audio_file_blips)
play_tts(tts_target, current_target.listeners, audio_file, audio_file_blips, current_target.language, current_target.message_range, current_target.volume_offset, FALSE, null, current_target.audio_length, current_target.audio_length_blips)
completed_tts_messages[current_target.identifier] = list("ref" = current_target, "expiry_time" = world.time + 300)
play_tts(
target = tts_target,
listeners = current_target.listeners,
audio = audio_file,
audio_blips = audio_file_blips,
language = current_target.language,
range = current_target.message_range,
volume_offset = current_target.volume_offset,
ignore_observers = FALSE,
source_speaker = null,
audio_length = current_target.audio_length,
audio_length_blips = current_target.audio_length_blips
)
completed_tts_messages[current_target.identifier] = list(
TTS_REQUEST_REF = current_target,
TTS_REQUEST_EXPIRE = world.time + 30 SECONDS,
)
if(length(data) != 1)
var/datum/tts_request/next_target = data[2]
next_target.when_to_play = world.time + current_target.audio_length
@@ -384,43 +396,50 @@ SUBSYSTEM_DEF(tts)
queued_tts_messages[tts_target] += arbritrary_delay
SHIFT_DATA_ARRAY(queued_tts_messages, tts_target, data)
for(var/identifier in queued_radio_messages)
for(var/identifier, radio_list in queued_radio_messages)
if(MC_TICK_CHECK)
return
if(completed_tts_messages[identifier])
var/list/all_radios = queued_radio_messages[identifier]
for(var/radio in all_radios)
var/list/hearers = all_radios[radio]
if(!istext(radio) && isweakref(radio))
var/datum/weakref/weakref = radio
var/obj/radio_obj = weakref?.resolve()
if(radio_obj && QDELETED(radio_obj))
queued_radio_messages[identifier].Remove(radio)
queued_radio_messages_compression[identifier].Remove(radio)
continue
if(!completed_tts_messages[identifier])
continue
var/datum/tts_request/tts_request = completed_tts_messages[identifier]["ref"]
var/sound/audio_file
var/sound/audio_file_blips
if(queued_radio_messages_compression[identifier] > 30)
audio_file = new(tts_request.audio_file_radio_gibberish)
else
audio_file = new(tts_request.audio_file_radio)
audio_file_blips = new(tts_request.audio_file_blips_radio)
play_tts(radio == TTS_GHOST_RADIO ? null : radio, hearers, audio_file, audio_file_blips, tts_request.language, INFINITY, tts_request.volume_offset, ignore_observers = TRUE, source_speaker = tts_request.target, audio_length = tts_request.audio_length_radio, audio_length_blips = tts_request.audio_length_blips_radio, volume_preference = /datum/preference/numeric/volume/sound_tts_radio_volume, volume_signal = COMSIG_MOB_TTS_RADIO_VOLUME_PREFERENCE_APPLIED)
queued_radio_messages.Remove(identifier)
completed_tts_messages.Remove(identifier)
queued_radio_messages_compression.Remove(identifier)
for(var/radio, hearer_list in radio_list)
if(isweakref(radio))
var/datum/weakref/weakref = radio
var/obj/radio_obj = weakref.resolve()
if(QDELETED(radio_obj))
queued_radio_messages[identifier] -= radio
continue
var/datum/tts_request/tts_request = completed_tts_messages[identifier][TTS_REQUEST_REF]
var/sound/audio_file = new( \
queued_radio_messages_compression[identifier] > COMPRESSION_REPLACE_CHARACTER_THRESHOLD \
? tts_request.audio_file_radio_gibberish \
: tts_request.audio_file_radio \
)
var/sound/audio_file_blips = new(tts_request.audio_file_blips_radio)
play_tts(
target = radio == TTS_GHOST_RADIO ? null : radio,
listeners = hearer_list,
audio = audio_file,
audio_blips = audio_file_blips,
language = tts_request.language,
range = INFINITY,
volume_offset = tts_request.volume_offset,
ignore_observers = TRUE,
source_speaker = tts_request.target,
audio_length = tts_request.audio_length_radio,
audio_length_blips = tts_request.audio_length_blips_radio,
volume_preference = /datum/preference/numeric/volume/sound_tts_radio_volume,
volume_signal = COMSIG_MOB_TTS_RADIO_VOLUME_PREFERENCE_APPLIED,
)
clear_radio_message(identifier)
for(var/identifier, request in completed_tts_messages)
if(MC_TICK_CHECK)
return
if (completed_tts_messages[identifier]["expiry_time"] >= world.time + 300)
completed_tts_messages[identifier]["ref"] = null
completed_tts_messages[identifier] = null
completed_tts_messages.Remove(identifier)
queued_radio_messages.Remove(identifier)
queued_radio_messages_compression.Remove(identifier)
if (world.time < completed_tts_messages[identifier][TTS_REQUEST_EXPIRE])
continue
clear_radio_message(identifier)
#undef TTS_ARBRITRARY_DELAY
@@ -474,6 +493,12 @@ SUBSYSTEM_DEF(tts)
else
queued_http_messages.insert(current_request)
/datum/controller/subsystem/tts/proc/clear_radio_message(identifier)
completed_tts_messages[identifier] -= TTS_REQUEST_REF // don't stick around
completed_tts_messages -= identifier
queued_radio_messages -= identifier
queued_radio_messages_compression -= identifier
/// Helper to get a random TTS voice for a certain gender. Passing no gender just results in a random voice.
/datum/controller/subsystem/tts/proc/random_tts_voice(gender = NEUTER)
if(!tts_enabled)
@@ -632,32 +657,33 @@ SUBSYSTEM_DEF(tts)
else
return request.is_complete() && request_blips.is_complete() && request_blips_radio.is_complete() && request_radio.is_complete() && request_radio_gibberish.is_complete()
/proc/filter_tts_listeners(list/listeners, radio_frequency = null)
if(!SStts.tts_enabled || !listeners)
return
#undef TTS_REQUEST_REF
#undef TTS_REQUEST_EXPIRE
if(isweakref(listeners))
listeners = list(listeners)
var/list/filtered_listeners = list()
/**
* Checks if the passed mob can hear radio TTS
*
* * hearer - The mob to check if they can hear radio TTS
* * radio_frequency - The frequency of the radio TTS message
*/
/proc/can_hear_radio_tts(mob/hearer, radio_frequency)
if(!SStts.tts_enabled)
return FALSE
for(var/datum/weakref/listener as anything in listeners)
if(!isweakref(listener))
continue
var/mob/possible_listener = listener?.resolve()
if(!ismob(possible_listener) || !possible_listener.client)
continue
var/tts_pref = possible_listener.client?.prefs.read_preference(/datum/preference/choiced/sound_tts)
var/radio_tts_pref = possible_listener.client?.prefs.read_preference(/datum/preference/choiced/sound_tts_radio)
if(tts_pref == TTS_SOUND_OFF)
continue
if(isliving(possible_listener) && (possible_listener.stat >= UNCONSCIOUS || HAS_TRAIT(possible_listener, TRAIT_DEAF)))
continue
if(radio_tts_pref == TTS_SOUND_NO_RADIO)
continue
if(radio_tts_pref == TTS_SOUND_DEPARTMENTAL_RADIO && radio_frequency == FREQ_COMMON) // don't give them the full common firehose if they turned it off
continue
filtered_listeners += listener
if(HAS_TRAIT(hearer, TRAIT_DEAF))
return FALSE
return filtered_listeners
var/tts_pref = hearer.client?.prefs.read_preference(/datum/preference/choiced/sound_tts) || TTS_SOUND_OFF
if(tts_pref == TTS_SOUND_OFF)
return FALSE
var/radio_tts_pref = hearer.client?.prefs.read_preference(/datum/preference/choiced/sound_tts_radio) || TTS_SOUND_NO_RADIO
switch(radio_tts_pref)
if(TTS_SOUND_NO_RADIO)
return FALSE
if(TTS_SOUND_DEPARTMENTAL_RADIO) // don't give them the full common firehose if they turned it off
return radio_frequency != FREQ_COMMON
return TRUE
#undef SHIFT_DATA_ARRAY