Adventure Game Studio

AGS Support => Advanced Technical Forum => Topic started by: Hernald on Fri 02/11/2012 04:01:38

Title: Error: SpriteCache::removeOldest - does anyone have any advice?
Post by: Hernald on Fri 02/11/2012 04:01:38
I get the following message as my game crashes about once every playthrough
Quote
An internal error has occured. Please note down the following information.
If the problem persists, please post the details on the AGS technical forum.
(ACI Version 3.21.1115)

Error: SpriteCache::removeOldest: Attempted to remove sprite 1200 that does not exist

This problem has been raised before on the forums but I haven't seen a solution.
Any suggestions?

P.S. I am using dynamic sprites. What I've noticed is that the crashes tend to happen shortly after a Changeview, but not always the same one, and when I resume the game from the last save it doesn't crash in the same place.
Title: Re: Error: SpriteCache::removeOldest - does anyone have any advice?
Post by: Crimson Wizard on Sun 04/11/2012 14:57:38
Well, the error message suggests that there was an attempt to delete same sprite twice.
On other hand, it happens deep in the engine, which does not look good (maybe AGS cannot detect problem before it screws things up badly).
This may be because of mistake in your script, or in AGS, or both.

I think best thing would be to post your script here so others could check it. Specifically parts:
- where you create dynamic sprites
- where you delete dynamic sprites
- where you call ChangeView (since you think this is related to crash).
Title: Re: Error: SpriteCache::removeOldest - does anyone have any advice?
Post by: Hernald on Sun 04/11/2012 16:31:44
Thanks for getting back to me Crimson. I will post some selected highlights of the code here over the next few days.

...Okay. Having taken another look at my code I realize there should probably be a lot more Deletes in it. I will get onto that;
but as the problem is probably the Deletes I have put in rather than the Deletes I have left out I will focus on them.

There are a couple of instances in my Speak code where vOldSprite is declared globally:
Code (AGS) Select

// Speak: Say with a background panel.

void Speak(this Character*, String message) {
  int wid;
  int hei;
  int lines;
  int maxwid = 428;
  int lesswid = 290;//257

  int newwid;
 
  int leftside = 160;
  int rightside = 480;

  int savewid;
 
  int char_width;
  int char_height;
  ViewFrame *char_frame;
  DynamicSprite *char_sprite;
 
  bool HotsVis;
 
  // Get Character dimensions.

  char_frame = Game.GetViewFrame(this.View, this.Loop, this.Frame);
  char_sprite= DynamicSprite.CreateFromExistingSprite(char_frame.Graphic);
  char_width=char_sprite.Width;
  char_height=char_sprite.Height;
   
  if (char_sprite!=null) char_sprite.Delete();

// Get speach position & dimensions
  int sayx;
  int sayy;
  sayx=this.x;
  sayy=this.y-(char_height*this.Scaling/100)-this.z;



  wid=GetTextWidth(message, eFontSpeechOutline);
 
 
  savewid=wid;
  newwid = maxwid;
  if (this.x<leftside+GetViewportX()||this.x>rightside+GetViewportX()) newwid=lesswid;
  if (wid>newwid)wid = newwid;
 
  if (this.x-(wid/2)<GetViewportX()) sayx=GetViewportX()+(wid/2);
  if (this.x+(wid/2)>640+GetViewportX()) sayx=640+GetViewportX()-(wid/2);

  hei=GetTextHeight(message, eFontSpeechOutline, wid);
  hei+=5;
  if (sayy-hei<8) sayy=hei+8;

// Set size of word balloon
  ViewFrame* rectframe;
  DynamicSprite* rectsprite;

  rectframe=Game.GetViewFrame(cRectangle.View,cRectangle.Loop, 0);
  rectsprite=DynamicSprite.CreateFromExistingSprite(1116);
  rectsprite.Resize(wid, hei);
  rectframe.Graphic=rectsprite.Graphic;
  if (vOldSprite!=null) vOldSprite.Delete();
  vOldSprite=rectsprite;

  if (gHotSpot.Visible) {
    HotsVis=true;
    gHotSpot.Visible=false;
  }
  else {
  HotsVis=false;
  }
 
 
 
// Position message
  cRectangle.Transparency=100;
  cRectangle.ChangeRoom(this.Room, sayx, sayy);
  cRectangle.Baseline=480;

// Show message
  cRectangle.Transparency=30;
  this.Say(message);
  cRectangle.Transparency=100;
 
  if (HotsVis) gHotSpot.Visible=true;

}


The second place dynamic sprites are deleted is in my amended version of BackgroundSpeech where vOldSprite1 and vOldSprite2 are global:
Code (AGS) Select

// Copyright (C) 2007-2009 Tom Vandepoele
//-------------------------------------------------------------------

AudioChannel* Channel[];
bool BgTalkActive[];
Overlay* Overlays[];
int Views[];
int Timers[];

function game_start()
{
   int ch_idx;
   Channel = new AudioChannel[Game.CharacterCount];
   BgTalkActive = new bool[Game.CharacterCount];
   Overlays = new Overlay[Game.CharacterCount];
   Views = new int[Game.CharacterCount];
   Timers = new int[Game.CharacterCount];
   
   ch_idx = 0;
   while (ch_idx < Game.CharacterCount)
   {
      BgTalkActive[ch_idx] = false;
      ch_idx++;
   }
}

// monkey_05_06 ... CreateTextual with Alignment!

import Overlay* CreateTextualOverlayAligned(int x, int y, int width, FontType font, int color, String text, Alignment=eAlignCentre);

Overlay *textOverlay;

Overlay* CreateTextualOverlayAligned(int x, int y, int width, FontType font, int color, String text, Alignment align) {
  int height = GetTextHeight(text, font, width);
  DynamicSprite *sprite = DynamicSprite.Create(width, height);
  DrawingSurface *surface = sprite.GetDrawingSurface();
  surface.DrawingColor = color;
  surface.DrawStringWrapped(0, 0, width, font, align, text);
  surface.Release();
  Overlay *newOverlay = Overlay.CreateGraphical(x, y, sprite.Graphic, true);
  if (sprite!=null) sprite.Delete();
  return newOverlay;
}

function ShowTextCentered(String text) {
  int width = GetTextWidth(text, Game.NormalFont);
  int height = GetTextHeight(text, Game.NormalFont, width);
  int x = (System.ViewportWidth / 2) - (width / 2);
  int y = (System.ViewportHeight / 2) - (height / 2);
  textOverlay = CreateTextualOverlayAligned(x, y, width, Game.NormalFont, 17238, text);
}

/**
* Have the character say (animation + sound) something in the background.
* Note! The talking character will not have lip sync to either the audio or the text.
**/

function StopSayInBackground(this Character*)
{
   if ((BgTalkActive[this.ID]) &&
       (null != Overlays[this.ID]))
   {
      if (null != Channel[this.ID])
      {
         Channel[this.ID].Stop();
      }
     
      if (Overlays[this.ID].Valid)
      {
         Overlays[this.ID].Remove();
      }
     
      if (0 != Views[this.ID])
      {
         this.UnlockView();
      }
     
      BgTalkActive[this.ID] = false;
     
      // MAKE RECTANGLE INVISIBLE
      if (this.GetProperty("background_rect")==1) {
        cRectangle1.Transparency=100;
        if (vOldSprite1!=null) vOldSprite1.Delete();
      }
      else if(this.GetProperty("background_rect")==2) {
        cRectangle2.Transparency=100;
        if(vOldSprite2!=null) vOldSprite2.Delete();
      }
   }


}

function SayInBackground(this Character*,  String message, AudioClip *clip, int view, AudioPriority priority)
{
   this.StopSayInBackground();
   
   int text_width;

   text_width = GetTextWidth(message, Game.SpeechFont) * 2;
 
   if (text_width > (System.ViewportWidth /2))
   {
      text_width = System.ViewportWidth /2;
   }


   int text_height = GetTextHeight(message, Game.SpeechFont, text_width);

   
   // Get Character dimensions
   
   int char_width;
   int char_height;
   ViewFrame *char_frame;
   DynamicSprite *char_sprite;
   
   char_frame = Game.GetViewFrame(this.View, this.Loop, this.Frame);
   char_sprite= DynamicSprite.CreateFromExistingSprite(char_frame.Graphic);
   char_width=char_sprite.Width;
   char_height=char_sprite.Height;
   
   if (char_sprite!=null) char_sprite.Delete();
     
   
   /* The x position of the overlay is calculated by the character's sprite width and the text width   */
   /* The y position of the overlay is calculated by the character's sprite height and the text height */

   int text_x = this.x  + (char_width / 2) - text_width;

   if (text_x + text_width > 631 ) {
     text_x = 631 - text_width;
   }
   else if (text_x < 8 ) {
     text_x = 8;
   }

   int text_y = this.y - this.z - (char_height + text_height);
   
   if (text_y < 8){
     text_y = 8;
   }
   else if (text_y + text_height > 471) {
      text_y = 471 - text_height;
   }
 
// GET SIZE AND POSITION OF RECTANGLE, PLACE IN ROOM AND MAKE VISIBLE
   Character* cThisRect;
   int RectNum;
   int ThisSprite;
   ViewFrame* rectframe;
   DynamicSprite* rectsprite;
   if (this.GetProperty("background_rect")==1) {
     cThisRect=cRectangle1;
     ThisSprite=1117;
     RectNum=1;
   }
   else if(this.GetProperty("background_rect")==2) {
     cThisRect=cRectangle2;
     ThisSprite=1118;
     RectNum=2;
   }
   else {
     Display("Background Rectangle for Character SayInBackground is not set or invalid.");
   }


   rectframe=Game.GetViewFrame(cThisRect.View,cThisRect.Loop, 0);

   
   rectsprite=DynamicSprite.CreateFromExistingSprite(ThisSprite);
   rectsprite.Resize(text_width,text_height);
   rectframe.Graphic=rectsprite.Graphic;
   if (RectNum==1) {
     vOldSprite1=rectsprite;
   }
   else {
     vOldSprite2=rectsprite;
   }
   Wait(1);


// Position message
   cThisRect.Transparency=100;
   cThisRect.ChangeRoom(this.Room,text_x+(text_width/2),text_y+text_height);
   cThisRect.Baseline=480;

// Show message
   cThisRect.Transparency=30;

   //Overlays[this.ID] = Overlay.CreateTextual(text_x, text_y, text_width, Game.SpeechFont, this.SpeechColor, message);
   Overlays[this.ID] = CreateTextualOverlayAligned(text_x, text_y, text_width, Game.SpeechFont, this.SpeechColor, message, eAlignCentre);

   Timers[this.ID] = GetGameSpeed() * (message.Length / 10);

   Channel[this.ID] = null;
   if (null != clip)
   {
      Channel[this.ID] = clip.Play(priority);
   }
   
   if (null != Overlays[this.ID])
   {
      Views[this.ID] = view;
      if (0 != view)
      {
         this.LockView(view);
      }
     
      this.Animate(this.Loop, this.SpeechAnimationDelay, eRepeat, eNoBlock);
      BgTalkActive[this.ID] = true;
   }
}

function BackgroundTalkActive(this Character*)
{
   return BgTalkActive[this.ID];
}

function repeatedly_execute()
{
   int ch_idx = 0;
   
   while (ch_idx < Game.CharacterCount)
   {
      if ((BgTalkActive[ch_idx]) &&
          (null != Overlays[ch_idx]))
      {
         if (Timers[ch_idx] > 0)
         {
            Timers[ch_idx]--;
         }
         
         if (null != Channel[ch_idx])
         {
            if (Channel[ch_idx].PlayingClip == null)
            {
               Channel[ch_idx] = null;
            }
         }
         
         if ((0 == Timers[ch_idx]) &&
             (null == Channel[ch_idx]))
         {
            Overlays[ch_idx].Remove();
           
         // MAKE RECTANGLE INVISIBLE
            if (character[ch_idx].GetProperty("background_rect")==1) {
              cRectangle1.Transparency=100;
              if (vOldSprite1!=null) vOldSprite1.Delete();
            }
            else if(character[ch_idx].GetProperty("background_rect")==2) {
              cRectangle2.Transparency=100;
              if (vOldSprite2!=null) vOldSprite2.Delete();
            }
           
            Overlays[ch_idx] = null;
            if (0 != Views[ch_idx])
            {
               character[ch_idx].UnlockView();
            }
            BgTalkActive[ch_idx] = false;
         }
      }
     
      ch_idx++;
   }
}


I expect I have made a beginners error here, but the fact that CJ was taking an interest in this error message a year or so ago prompted me to put it in Advanced. I will get back to putting Deletes where they should be and hope that someone can point out what I've done wrong here.
Incidentally, I am using the Credits and Flashlight modules as well but I haven't tampered with them (much) so I expect the problem doesn't lie there.

Thanks in advance for your help.
Title: Re: Error: SpriteCache::removeOldest - does anyone have any advice?
Post by: Hernald on Mon 05/11/2012 21:01:04
On second thoughts; I think I will wait to see what people say about the code I have posted before adding any more Deletes.
Title: Re: Error: SpriteCache::removeOldest - does anyone have any advice?
Post by: Khris on Mon 05/11/2012 21:42:03
I didn't look at the second piece of code but what I noticed is that you can replace this:
Code (ags) Select
  char_frame = Game.GetViewFrame(this.View, this.Loop, this.Frame);
  char_sprite= DynamicSprite.CreateFromExistingSprite(char_frame.Graphic);
  char_width=char_sprite.Width;
  char_height=char_sprite.Height;
  if (char_sprite!=null) char_sprite.Delete();


with this:
Code (ags) Select
  char_frame = Game.GetViewFrame(this.View, this.Loop, this.Frame);
  char_width = Game.SpriteWidth[char_frame.Graphic];
  char_height = Game.SpriteHeight[char_frame.Graphic];


That should reduce the number of DynSprites being created and deleted significantly.
Title: Re: Error: SpriteCache::removeOldest - does anyone have any advice?
Post by: Hernald on Mon 05/11/2012 22:09:25
Thanks for that pointer Khris, I'll get onto it.

... That made my code more efficient, unfortunately it didn't stop my game from crashing. This time it wasn't particularly close to a Changeview.
I will keep trying things out, let me know if there's anything else I can tell you.
Title: Re: Error: SpriteCache::removeOldest - does anyone have any advice?
Post by: Hernald on Tue 06/11/2012 11:00:41
Could the problem be caused by making a Character's View out of dynamic sprites?
Title: Re: Error: SpriteCache::removeOldest - does anyone have any advice?
Post by: Crimson Wizard on Tue 06/11/2012 11:32:02
I am actually wondering, what happens after you delete the Old Sprites. You don't seem to restore View Frame's graphic value to original then.
I am thinking, what if AGS tries to remove the Frame's sprite from cache while it was already deleted (as being a dynamic sprite)...
What will happen if you change the Rectangle's View to default (static) sprites before deleting dynamic ones?
(I am speaking about StopSayInBackground function)
Title: Re: Error: SpriteCache::removeOldest - does anyone have any advice?
Post by: Hernald on Tue 06/11/2012 11:52:33
Good idea! I'll try that and let you know how it goes.

... I just played through my game without it crashing!
This was how I changed the code:
Code (AGS) Select

      // MAKE RECTANGLE INVISIBLE
      if (this.GetProperty("background_rect")==1) {
        cRectangle1.Transparency=100;
        tempvf = Game.GetViewFrame(cRectangle1.View, cRectangle1.Loop, cRectangle1.Frame);
        tempvf.Graphic=1117;
        if (vOldSprite1!=null) vOldSprite1.Delete();
      }
      else if(this.GetProperty("background_rect")==2) {
        cRectangle2.Transparency=100;
        tempvf = Game.GetViewFrame(cRectangle2.View, cRectangle2.Loop, cRectangle2.Frame);
        tempvf.Graphic=1118;
        if(vOldSprite2!=null) vOldSprite2.Delete();
      }

Thanks for the advice Crimson Wizard.
I will play the game through again two or three times in the next week and if all's well I'll mark this as solved.
Title: Re: Error: SpriteCache::removeOldest - does anyone have any advice?
Post by: Crimson Wizard on Wed 07/11/2012 00:13:36
That's very good.

I'd say we uncovered a mistake in AGS engine here. It should behave differently, probably remove dynamic sprite from cache when it is deleted, and change Frame's graphic to 0 for safety reasons. Gonna write this down...
E: added issue: http://www.adventuregamestudio.co.uk/forums/index.php?issue=362.0
Title: Re: Error: SpriteCache::removeOldest - does anyone have any advice?
Post by: Calin Leafshade on Wed 07/11/2012 12:31:39
I've played around with using dynamicsprites in character views a couple of times and it is very unstable but I could never pin down the root cause. Definitely something to investigate.