From 3fffe061d5a589eb2e0d55602ce0594006e595c0 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Mon, 6 Apr 2026 06:55:27 +0500 Subject: [PATCH] engine: client: switch to dynamically allocating vox words, freeing up 320 Kb of memory --- engine/client/s_main.c | 16 ++++++++++------ engine/client/s_mix.c | 2 +- engine/client/s_vox.c | 26 ++++++++++++++++++-------- engine/client/sound.h | 3 +-- 4 files changed, 30 insertions(+), 17 deletions(-) diff --git a/engine/client/s_main.c b/engine/client/s_main.c index c57a6722..af73cdc9 100644 --- a/engine/client/s_main.c +++ b/engine/client/s_main.c @@ -171,11 +171,16 @@ S_FreeChannel */ void S_FreeChannel( channel_t *ch ) { + // free the currently loaded word's audio cache before nuking the channel + if( ch->words ) + VOX_FreeWord( ch ); + ch->sfx = NULL; ch->name[0] = '\0'; ch->flags = 0; ch->forced_end = ch->sample = 0.0; ch->data = NULL; + Mem_Free2( &ch->words ); SND_CloseMouth( ch ); } @@ -256,7 +261,7 @@ static int SND_GetChannelTimeLeft( const channel_t *ch ) if( FBitSet( ch->flags, FL_CHAN_FINISHED ) || !ch->sfx || !ch->sfx->cache ) return 0; - if( FBitSet( ch->flags, FL_CHAN_IS_SENTENCE )) // sentences are special, count all remaining words + if( ch->words ) // sentences are special, count all remaining words { int i; @@ -268,7 +273,7 @@ static int SND_GetChannelTimeLeft( const channel_t *ch ) // here we count all remaining words, stopping if no sfx or sound file is available // see VOX_LoadWord - for( i = ch->word_index + 1; i < ARRAYSIZE( ch->words ); i++ ) + for( i = ch->word_index + 1; i < CVOXWORDMAX; i++ ) { wavdata_t *sc; int end; @@ -440,7 +445,7 @@ static qboolean S_MaybeAlterChannel( channel_t *ch, int entnum, int entchannel, // if no sfx passed, check if it's a sentence if( !sfx ) { - if( !FBitSet( ch->flags, FL_CHAN_IS_SENTENCE )) + if( !ch->words ) return false; } else @@ -876,7 +881,6 @@ void S_AmbientSound( const vec3_t pos, int ent, sound_t handle, float fvol, floa // load regular or stream sound pSource = S_LoadSound( sfx ); ch->sfx = sfx; - ClearBits( ch->flags, FL_CHAN_IS_SENTENCE ); ch->name[0] = '\0'; } @@ -945,7 +949,7 @@ int S_GetCurrentStaticSounds( soundlist_t *pout, int size ) if( ch->entchannel != CHAN_STATIC || !ch->sfx || !ch->sfx->name[0] ) continue; - if( FBitSet( ch->flags, FL_CHAN_IS_SENTENCE ) && ch->name[0] ) + if( ch->words && ch->name[0] ) Q_strncpy( pout->name, ch->name, sizeof( pout->name )); else Q_strncpy( pout->name, ch->sfx->name, sizeof( pout->name )); @@ -995,7 +999,7 @@ int S_GetCurrentDynamicSounds( soundlist_t *pout, int size ) if( ch->entchannel == CHAN_STATIC && looped && !Host_IsQuakeCompatible()) continue; // never serialize static looped sounds. It will be restoring in game code - if( FBitSet( ch->flags, FL_CHAN_IS_SENTENCE ) && ch->name[0] ) + if( ch->words && ch->name[0] ) Q_strncpy( pout->name, ch->name, sizeof( pout->name )); else Q_strncpy( pout->name, ch->sfx->name, sizeof( pout->name )); diff --git a/engine/client/s_mix.c b/engine/client/s_mix.c index fd64c6e5..13cbf84d 100644 --- a/engine/client/s_mix.c +++ b/engine/client/s_mix.c @@ -318,7 +318,7 @@ static int S_MixNormalChannels( portable_samplepair_t *dst, int end, int rate ) num_mixed_channels++; - if( FBitSet( ch->flags, FL_CHAN_IS_SENTENCE )) + if( ch->words ) { VOX_MixChannelToBuffer( dst, ch, num_samples, rate, pitch ); diff --git a/engine/client/s_vox.c b/engine/client/s_vox.c index 3f87501c..3216d9a4 100644 --- a/engine/client/s_vox.c +++ b/engine/client/s_vox.c @@ -191,7 +191,7 @@ void VOX_SetChanVol( channel_t *ch ) { voxword_t *word; - if( !FBitSet( ch->flags, FL_CHAN_IS_SENTENCE ) || FBitSet( ch->flags, FL_CHAN_SENTENCE_FINISHED )) + if( !ch->words || FBitSet( ch->flags, FL_CHAN_SENTENCE_FINISHED )) return; word = &ch->words[ch->word_index]; @@ -207,7 +207,7 @@ float VOX_ModifyPitch( channel_t *ch, float pitch ) { voxword_t *word; - if( !FBitSet( ch->flags, FL_CHAN_IS_SENTENCE ) || FBitSet( ch->flags, FL_CHAN_SENTENCE_FINISHED )) + if( !ch->words || FBitSet( ch->flags, FL_CHAN_SENTENCE_FINISHED )) return pitch; word = &ch->words[ch->word_index]; @@ -449,10 +449,18 @@ void VOX_LoadSound( channel_t *ch, const char *pszin ) int i, j; int num_words; voxword_t default_voxword; + voxword_t words_buf[CVOXWORDMAX + 1]; // local scratch: parsed words + null terminator if( !pszin ) return; + // free any existing words from a previous sentence on this channel + if( ch->words ) + { + VOX_FreeWord( ch ); + Mem_Free2( &ch->words ); + } + psz = VOX_LookupString( pszin ); if( !psz ) @@ -480,12 +488,13 @@ void VOX_LoadSound( channel_t *ch, const char *pszin ) num_words = VOX_ParseString( buffer, rgpparseword ); VOX_MakeDefaultWordParams( &default_voxword ); + memset( words_buf, 0, sizeof( words_buf )); for( i = 0, j = 0; i < num_words; i++ ) { char pathbuffer[MAX_SYSPATH]; - if( !VOX_ParseWordParams( rgpparseword[i], &ch->words[j], &default_voxword )) + if( !VOX_ParseWordParams( rgpparseword[i], &words_buf[j], &default_voxword )) continue; if( Q_snprintf( pathbuffer, sizeof( pathbuffer ), "%s%s", szpath, rgpparseword[i] ) < 0 ) @@ -495,18 +504,19 @@ void VOX_LoadSound( channel_t *ch, const char *pszin ) } qboolean in_cache = false; - ch->words[j].sfx = S_FindName( pathbuffer, &in_cache ); + words_buf[j].sfx = S_FindName( pathbuffer, &in_cache ); if( in_cache ) - SetBits( ch->words[j].flags, FL_VOXWORD_IN_CACHE ); + SetBits( words_buf[j].flags, FL_VOXWORD_IN_CACHE ); j++; } - ch->words[j].sfx = NULL; + // words_buf[j].sfx is already NULL from the memset — null terminator + ch->words = Mem_Malloc( sndpool, ( j + 1 ) * sizeof( voxword_t )); + memcpy( ch->words, words_buf, ( j + 1 ) * sizeof( voxword_t )); + ch->sfx = ch->words[0].sfx; ch->word_index = 0; - SetBits( ch->flags, FL_CHAN_IS_SENTENCE ); - VOX_LoadWord( ch ); } diff --git a/engine/client/sound.h b/engine/client/sound.h index 7051bc9b..1a5a2315 100644 --- a/engine/client/sound.h +++ b/engine/client/sound.h @@ -94,7 +94,6 @@ typedef struct rawchan_s #define FL_CHAN_USE_LOOP BIT( 0 ) // don't loop default and local sounds #define FL_CHAN_STATIC_SOUND BIT( 1 ) // use origin instead of fetching entnum's origin #define FL_CHAN_LOCAL_SOUND BIT( 2 ) // it's a local menu sound (not looped, not paused) -#define FL_CHAN_IS_SENTENCE BIT( 3 ) // bit indicating vox sentence #define FL_CHAN_SENTENCE_FINISHED BIT( 4 ) // if set, finished playing sentence #define FL_CHAN_FINISHED BIT( 5 ) // if set, finished playing single word @@ -123,7 +122,7 @@ typedef struct channel_s double sample; double forced_end; wavdata_t *data; - voxword_t words[CVOXWORDMAX]; + voxword_t *words; // dynamically allocated, (num_words + 1) entries, null sfx terminates // TODO: add reserved bytes } channel_t;