Problem with transient memory in sound

Hi everyone. I do breakout clone and use handmade hero series.Now I have memory bug. So, I have done sound mixer like casey did in 140 episode. I trigger a brick collision sound when ball colides with bricks.
1
2
3
4
5
6
7
8
      if(checkCollision(ball.pos.x, ball.pos.y,
			ball.pos.x + gameState->ballWidth, ball.pos.y + gameState->ballHeight,
		        brick->pos.x, brick->pos.y,
			brick->pos.x + (gameState->brickWidth-1.0f), brick->pos.y+(gameState->brickHeight-1.0f)))
	{
	  // NOTE(shvayko): collision sound for brick-ball
	  playSound(gameState, "bloop_00.wav");
        }

So, every time I do this call my memory grows up and I don't know why cuz I use transient memory allocation.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
void gameGetSoundSamples(Game_Memory *gameMemory, Game_sound_output *gameSoundBuffer)
{
  Game_State *gameState = (Game_State*)gameMemory->permanentStorage; 
  Transient_state *transState = (Transient_state *)gameMemory->transientStorage;
  
  Temp_memory mixerMemory = beginTempMemory(&transState->transArena);
  
  f32 *realChannel0 = (f32*)pushArray(&transState->transArena, f32, gameSoundBuffer->samplesToOutput);
  f32 *realChannel1 = (f32*)pushArray(&transState->transArena, f32, gameSoundBuffer->samplesToOutput);

  {
    f32 *dest0 = realChannel0;
    f32 *dest1 = realChannel1;                  
    
    for(DWORD sampleIndex = 0;
	sampleIndex < gameSoundBuffer->samplesToOutput;
	sampleIndex++)
      {
	*dest0++ = 0;
	*dest1++ = 0;
      }
  }  
  // NOTE(shvayko): sound mixer

  for(Playing_sound **playingSoundPtr = &gameState->firstPlayingSound;
      *playingSoundPtr; )
    {
      Playing_sound *playingSound = *playingSoundPtr;
      bool soundIsFinished = false;
      if(playingSound->loadedSound.sampleCount)
	{
	  f32 *dest0 = realChannel0;
	  f32 *dest1 = realChannel1;
	  f32 volume0 = playingSound->volume[0];
	  f32 volume1 = playingSound->volume[1];

	  u32 samplesToMix = gameSoundBuffer->samplesToOutput;
	  u32 samplesRemaining = playingSound->loadedSound.sampleCount - playingSound->samplesPlayed;
	
	  if(samplesToMix > samplesRemaining)
	    {
	      samplesToMix = samplesRemaining;
	    }
	
	  for(DWORD sampleIndex = playingSound->samplesPlayed;
	      sampleIndex < playingSound->samplesPlayed + samplesToMix;
	      sampleIndex++)
	    {
	      f32 sampleValue = playingSound->loadedSound.samples[0][sampleIndex];
	      *dest0++ = volume0 * sampleValue;
	      *dest1++ = volume1 * sampleValue;
	    }
	  playingSound->samplesPlayed += samplesToMix;
	  soundIsFinished = (playingSound->samplesPlayed == playingSound->loadedSound.sampleCount);
	}
      // NOTE(shvayko): sound gets killed	 
      if(soundIsFinished)
	{
	  *playingSoundPtr = playingSound->next;
	  playingSound->next = gameState->firstFreePlayingSound;
	  gameState->firstFreePlayingSound = playingSound;
	}
      else
	{
	  playingSoundPtr = &playingSound->next;
	}
    }
    
  {
    f32 *source0 = realChannel0;
    f32 *source1 = realChannel1;
    s16 *sampleDest = gameSoundBuffer->samples;  
    for(DWORD sampleIndex = 0; sampleIndex < gameSoundBuffer->samplesToOutput; sampleIndex++)
      {
	*sampleDest++ = (s16)(*source0++);
	*sampleDest++ = (s16)(*source1++);
      }
  }
  endTempMemory(mixerMemory);
  checkArena(&transState->transArena);
  //gameSoundOutput(gameSoundBuffer,gameMemory);

I have been debugging and all seems ok. At beggining I push memory in transient storage and then pop it .


Here playSound function
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
internal Playing_sound*
playSound(Game_State *gameState, char *music)
{
  if(!gameState->firstFreePlayingSound)
    {
      gameState->firstFreePlayingSound  = (Playing_sound*)pushStruct(&gameState->levelArena, Playing_sound);
      gameState->firstFreePlayingSound->next = 0;
    }
  Playing_sound *playingSound = gameState->firstFreePlayingSound;
  gameState->firstFreePlayingSound = playingSound->next;
  
  playingSound->volume[0] = 1.0f;
  playingSound->volume[1] = 1.0f;      
  playingSound->loadedSound = loadWAVEFile(music);
  playingSound->samplesPlayed = 0;  
  playingSound->next = gameState->firstPlayingSound;
  
  gameState->firstPlayingSound = playingSound;
  
  return playingSound;
}

Also I don't understand the point why we return Playing_sound* but when we call this function no one takes it.
Memory stuff ->
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
internal void
initArena(Memory_arena *arena, size_t size, u8 *base)
{
  arena->base = base;
  arena->used = 0;
  arena->size = size;
  arena->tempCount = 0;
}

#define pushStruct(arena, type) _pushSize(arena,sizeof(type))
#define pushArray(arena, type, num) _pushSize(arena, sizeof(type)*num)

inline void*
_pushSize(Memory_arena *arena, size_t size)
{
  void *result = (arena->base + arena->used);
  arena->used += size;
  return result;
}

inline Temp_memory
beginTempMemory(Memory_arena *arena)
{
  Temp_memory result;

  result.arena = arena;
  result.used = arena->used;
  arena->tempCount++;
  return result;
}

inline void
endTempMemory(Temp_memory tempMemory)
{
  Memory_arena *arena = tempMemory.arena;
  assert(arena->used >= tempMemory.used);
  arena->used = tempMemory.used;
  
  arena->tempCount--;
}

inline void
checkArena(Memory_arena *arena)
{
  assert(arena->tempCount == 0);
}

So, may be I misunderstand the point of transient storage or I have a bug? Thanks in advance!!! (Sorry for the poor english)

Edited by Roman on Reason: Initial post
General debugging
I would try commenting out all code between allocation (beginTempMemory) and deallocation (endTempMemory) in the arena. Then I would know for sure if the leak is caused by the arena allocation itself or from something sent to the system. If the leak goes away, activate more features while keeping symmetry until you know where the leak comes from. Try to have a 50/50 split between your suspected code sections to make an efficient binary search. If it's hard to remember, you can place comments in the code declaring where the defect did not come from. If nothing makes sense, it might me multiple defects (like two calls to the same defective function) or a combination of states triggering each other (like a pointer being overwritten from out-of-bound assignments).

Using a debugger
You can also try learning different memory debugging tools. Start with the ones in your favorite IDEs, which are easiest to learn and allow visualizing program execution while defects happen. Insert a breakpoint at the top of the function and then execute in debug mode until arriving at the place to start debugging.

Keep in mind that external memory tools will only show the actual memory allocated from the system, which may be missleading for arena allocation, which only takes a huge chunk when running out of memory.

Edited by Dawoodoz on
What does loadWAVEFile do? Where it gets memory?
It seems you are calling it every time you want to play sound? Maybe load wave file once, and then reuse it?
You are the best. Thanks. It was really dumb bug.
Also can you explain me this code. Why is that work? We declare arena within endTempMemory so it is stored in stack. Why is this function Influences at mixerMemory(Temp memory)
1
2
3
4
5
6
7
8
9
inline void
endTempMemory(Temp_memory tempMemory)
{
  Memory_arena *arena = tempMemory.arena;
  assert(arena->used >= tempMemory.used);
  arena->used = tempMemory.used;
  
  arena->tempCount--;
}

Thanks for your help. I guess visual studio bad in debugging memory problems. I have seen vs profiler and it is bad ;c
1
2
3
4
5
6
7
8
9
inline void
endTempMemory(Temp_memory tempMemory)
{
  Memory_arena *arena = tempMemory.arena;
  assert(arena->used >= tempMemory.used);
  arena->used = tempMemory.used;
  
  arena->tempCount--;
}


In that code, arena is just a local copy of the arena pointer to avoid having to type tempMemory.arena several times. It also makes it a little more obvious (in my opinion) that you're modifying the actual arena. You could have the same code as

1
2
3
4
5
6
7
inline void
endTempMemory(Temp_memory tempMemory)
{
  assert(tempMemory.arena->used >= tempMemory.used);
  tempMemory.arena->used = tempMemory.used;
  tempMemory.arena->tempCount--;
}


tempMemory.arena is a pointer to the arena, so you're accessing the actual arena, not a copy.
This is make sense. Thank you