Jump to content
Kayzee

Faster loadtimes with less memory or your Cache Back!

Recommended Posts

Hello everyone! I made a script! I have been having some performance problems when testing my game because of storing the files on a network drive, so I decided to write a widdle script to improve the cache!

 

#========================================================================
# ** Cache Back, by: KilloZapit
#------------------------------------------------------------------------
# Hey, anyone ever test or play VX ace games which are stored over a
# network connection? I do! And it does have a pretty noticeable delay
# sometimes when loading stuff or in battle.
#
# Sooo... I looked in to improving the cache. But the only script
# I found for "Cache Optimization" apparently was written by someone
# who didn't seam to realize why preloading all files and duplicating
# them whenever they are looked up is a bad idea (Hint: it has to do
# with ram useage and page swapping).
#
# Sooooo... I decided to make my own widdle script! And here it is!
# So what can it do? Welll, for starters it can set some bitmaps to
# be loaded once and never disposed! Only a few system images are by
# default though. It can also keep cached bitmaps around till the
# current scene ends instead of disposing them right away! Helpful for
# animations in battle! Last it automatically cleans up bitmaps that have
# not been used in a while! That will make it use less memory over time.
#
# I am not sure about the most optimal settings yet, but I am pretty
# happy with some of the speed improvements I get.
#------------------------------------------------------------------------
# Version 2: Tweaked the default settings, added some settings for
# printing stuff, and a garbage collection tweak to try and fix some
# problems with crashing.
#------------------------------------------------------------------------
# Version 3: Added the option to automatically precache all actor's 
# sprites or faces. Also added get_key and set_key methods for caching 
# objects besides loaded bitmaps such as dynamic graphics and stuff.
#------------------------------------------------------------------------
# Version 4: Little fix to recache tileset graphics if BITMAP_MAX_AGE is
# set. Otherwise switching between maps with the same tileset wouldn't
# count the ages right. Thankies to Galv for finding the bug!
#========================================================================
# Version 5: Redid the code to recache tileset graphics so that instead
# it disposes and recreates the whole spritesheet which should fix more
# incompatibilitys. Also added the DISPOSE_ON_NEWMAP option so users can
# turn off disposing things on map transfer entirly if needed. Thanks to
# RydiaMist for pointing out that recashing tilesets did nothing to help
# parallax and other sprites from being disposed incorrectly!
#========================================================================
module Cache
  
  # When this is true, cached bitmaps are not disposed normaly
  KEEP_DISPOSED_BITMAPS = true
  
  # When this is true disposed bitmaps in the cache are disposed when 
  # the current scene terminates. Try turning this on if there is too
  # much memory being used.
  BUFFER_DISPOSED_BITMAPS = false
  
  # When this is not null, every map change or return to the map scene,
  # all cached bitmaps have their age value increased by one. Bitmaps
  # with an age value over the max are disposed. The age value is reset
  # when the bitmap is loaded from the cache. 1 is the recommended 
  # minimum, otherwise lots of bitmap are likely to be disposed  and 
  # reloaded returning from menus.   
  BITMAP_MAX_AGE = 1 
  
  # Dispose bitmaps on transfering between maps if this is true.
  # Set this to false if you get disposed bitmap errors when transfering 
  # between maps.
  DISPOSE_ON_NEWMAP = true
  
  # Print messages when the cache is cleaned up if this is true.
  PRINT_CACHE_STATUS = true
  
  # Print messages when a bitmap is loaded in the cache if this is true.
  PRINT_LOADED_BITMAPS = true
  
  # Temporarily disables ruby garbage collection while disposing old
  # bitmaps. May or may not help stability.
  GARBAGE_COLLECTION_TWEAK = true
  
  # Precaches character sprites for all actors. Better to turn it off
  # if there are a lot of actors/sprites.
  PRECACHE_ACTOR_SPRITES = true
  # Same as above but for faces.
  PRECACHE_ACTOR_FACES = false
  
  
  # * New Method: run when the game starts and when the cache is cleared
  # Load any bitmaps you want to keep around here, and set keep_cached
  # on them to true like below.
  def self.precache
    system("IconSet").keep_cached = true
    system("Window").keep_cached = true
    
    for actor in $data_actors
      next unless actor
      if PRECACHE_ACTOR_SPRITES 
        character(actor.character_name).keep_cached = true
      end
      if PRECACHE_ACTOR_FACES
        face(actor.face_name).keep_cached = true
      end
    end if PRECACHE_ACTOR_SPRITES || PRECACHE_ACTOR_FACES
    
    if PRINT_CACHE_STATUS
      n = @cache.values.count {|bitmap| bitmap.keep_cached}
      puts("Cashe contains " + n.to_s + " precashed objects.")
    end
    
  end
  
  # * Alias: Load bitmap and set flags
  class << self
    alias load_bitmap_cache load_bitmap
  end
  def self.load_bitmap(folder_name, filename, hue = 0)
    bitmap = load_bitmap_cache(folder_name.downcase, filename.downcase, hue)
    bitmap.cached = true
    bitmap.age = 0
    bitmap
  end
  
  # * Overwriten Method: Clear Cache
  # Is this even ever used? Well it's here just incase.
  def self.clear
    @disposed_bitmaps = nil
    @cache ||= {}
    @cache.each {|bitmap| bitmap.cache_dispose rescue next}
    @cache.clear
    GC.start
    precache
    puts("Cleared Cache") if PRINT_CACHE_STATUS
  end
  
  # * New Method: Adds bitmap to an array to be disposed later
  def self.add_dispose(bitmap)
    @disposed_bitmaps ||= []
    @disposed_bitmaps |= [bitmap]
  end
  
  # * New Method: Dispose bitmaps needing to be disposed
  def self.do_dispose
    GC.disable if GARBAGE_COLLECTION_TWEAK
    # dispose disposed bitmaps for this scene
    # (mostly animations and stuff)
    if @disposed_bitmaps
      for bitmap in @disposed_bitmaps 
        bitmap.cache_dispose unless bitmap.disposed?
      end
      puts("Disposed of " + @disposed_bitmaps.size.to_s + " objects.") if PRINT_CACHE_STATUS
      @disposed_bitmaps = nil
    end
    # dispose bitmaps that haven't been used in a while.
    if BITMAP_MAX_AGE && SceneManager.scene_is?(Scene_Map)
      n = 0
      @cache.values.each do |bitmap|
        next if bitmap.keep_cached || bitmap.disposed?
        bitmap.age ||= 0
        if bitmap.age > BITMAP_MAX_AGE
          bitmap.cache_dispose
          n += 1
        else
          bitmap.age += 1 
        end
      end
      puts("Disposed of " + n.to_s + " old objects.") if PRINT_CACHE_STATUS
    end
    # Clean up cache hash, because I wanted to count the non-disposed
    # bitmaps during debugging anyway, so why not?
    @cache.delete_if do |key, bitmap|
      bitmap.disposed? && !bitmap.keep_cached
    end
    puts("Cache now contains " + @cache.size.to_s + " objects.") if PRINT_CACHE_STATUS
    if GARBAGE_COLLECTION_TWEAK 
      GC.enable 
      GC.start
    end
  end

  def self.set_key(key, value)
    unless include?(key)
      puts("Cache Key Set: " + key.to_s) if PRINT_CACHE_STATUS
      @cache[key] = value
    end
    value.cached = true
    value.age = 0
  end
  
  def self.get_key(key)
    return nil unless include?(key)
    value = @cache[key]
    value.age = 0
    value
  end

  if PRINT_LOADED_BITMAPS
    
  def self.normal_bitmap(path)
    unless include?(path)
      puts("Loading Bitmap: " + path)
      @cache[path] = Bitmap.new(path)
    end
    @cache[path]
  end
  
  end
  
end

class Bitmap
  
  # * Added Public Instance Variable: Flag set when a bitmap is cached
  attr_accessor :cached
  # * Added Public Instance Variable: Flag set to keep bitmap in memory
  attr_accessor :keep_cached
  # * Added Public Instance Variable: Bitmap age value
  attr_accessor :age
  
  # * Alias: Code run when a bitmap is erased/unloaded
  alias_method :cache_dispose, :dispose
  def dispose
    # Never dispose bitmaps with keep_cached set
    return if self.disposed? || @keep_cached
    # Don't despose chached bitmaps if the settings say to keep them
    if @cached && Cache::KEEP_DISPOSED_BITMAPS
      # Tell the cache to add this bitmap to it's list of bitmaps
      # to be disposed later (if BUFFER_DISPOSED_BITMAPS is true) 
      Cache.add_dispose(self) if Cache::BUFFER_DISPOSED_BITMAPS
    else
      cache_dispose
    end
  end
  
  # * Alias: clear flags when copying bitmaps
  alias_method :cache_dup, :dup
  def dup
    bitmap = cache_dup
    bitmap.cached = false
    bitmap.keep_cached = false
    bitmap
  end
 
  # * Alias: same as above (clone and dup are not QUITE the same)
  alias_method :cache_clone, :clone
  def clone
    bitmap = cache_clone
    bitmap.cached = false
    bitmap.keep_cached = false
    bitmap
  end   
  
end

class Scene_Base
  
  # * Alias: tell the cache to dispose stuff when the scene changes
  alias_method :cache_main_base, :main
  def main
    cache_main_base
    Cache.do_dispose
  end
  
end

class Game_Map
  
  # * Alias: tell the cache to dispose stuff when the map changes too
  alias_method :cache_setup_base, :setup
  def setup(map_id)
    if SceneManager.scene.is_a?(Scene_Map)
      SceneManager.scene.dispose_spriteset
      Cache.do_dispose
    end
    cache_setup_base(map_id)
    SceneManager.scene.create_spriteset if SceneManager.scene.is_a?(Scene_Map)
  end if Cache::DISPOSE_ON_NEWMAP
  
end

module DataManager

  class << self
    alias load_database_cache load_database
  end
  def self.load_database
    load_database_cache
    Cache.precache
  end

end

 



(Get it? The name is a pun! Hyuck hyuck hyuck!)

Edit: Now version 5! All things are related to it! (Principia Discordia said so)

Also: I made a small script to precache sounds too, but it requires this script and some fiddling to work:
 

 

# Addon to Cache Back and Audio Pump Up to cache sounds
module Cache
  
  class << self
    alias precache_bitmaps precache
  end
  def self.precache
    @cache ||= {}
    for sound in $data_system.sounds
      FMod::cache('Audio/SE/'+sound.name).keep_cached = true
    end
    precache_bitmaps
  end

end

module FMod
  
  def self.cache(name)
    # Get a valid file name
    filename = self.selectBGMFilename(name)
    # Create Sound or Stream and set initial values
    sound = @fmod.createSound(filename)
    return sound
  end
  
  class << self
    alias selectBGMFilename_cache_base selectBGMFilename
  end
  def self.selectBGMFilename(name)
    (@path_hash ||= {})[name.to_sym] ||= selectBGMFilename_cache_base(name)
  end
  
end

module FModEx
  
  class System
    
    alias_method :createSound_cache_base, :createSound
    def createSound(filename, mode = FMOD_DEFAULT_SOFTWARWE)
      unless sound = Cache.get_key(filename)
        puts("Loading Sound: " + filename)
        sound = createSound_cache_base(filename, mode)
        Cache.set_key(filename, sound)
      end
      sound
    end
    
  end
  
  class Sound
    # * Added Public Instance Variable: Flag set when a bitmap is cached
    attr_accessor :cached
    # * Added Public Instance Variable: Flag set to keep bitmap in memory
    attr_accessor :keep_cached
    # * Added Public Instance Variable: Bitmap age value
    attr_accessor :age
  
    # * Alias: Code run when a bitmap is erased/unloaded
    alias_method :cache_dispose, :dispose
    def dispose
      # Never dispose bitmaps with keep_cached set
      return if self.disposed? || @keep_cached
      # Don't despose chached bitmaps if the settings say to keep them
      if @cached && Cache::KEEP_DISPOSED_BITMAPS
        # Tell the cache to add this bitmap to it's list of bitmaps
        # to be disposed later (if BUFFER_DISPOSED_BITMAPS is true) 
        Cache.add_dispose(self) if Cache::BUFFER_DISPOSED_BITMAPS
      else
        cache_dispose
      end
    end
    
    def disposed?
      return true unless @handle && @handle > 0
      false
    end
    
  end
  
end

 



Mostly the problem is it doesn't work if you use the default sound system (channel 0).

Edit: I made a minor edit to the audio script to cache names too, which speeds it up quite a bit for me. :3

Edited by KilloZapit

Share this post


Link to post
Share on other sites

Not exactly... it isn't nearly that tidy most of the time. A good example are battle animation images which are loaded and disposed and then reloaded again and again. It usually either disposes of images right after they are used or just keeps them in the cache indefinitely.

Share this post


Link to post
Share on other sites

Hmm, so what kind of things should I be pre-caching in the script? Just graphics that get used a lot over and over throughout the game?

Edited by Ocedic

Share this post


Link to post
Share on other sites

Yeah, more or less. It might be a got idea not to precache too much though! Use too much memory and the os will run the game more slowly trying to manage it all.

Share this post


Link to post
Share on other sites

After a few tests, I'm pretty sure this script fixes the horrible delay in VE Damage Popup when too many numbers need to be produced to show damage. In short, you're the best. Ever.

Share this post


Link to post
Share on other sites

Well, I noticed it fixing some delay for popups for yanfly's battle engine too... I think it might be because of constantly reloading IconSet.png, as I don't think the numbers themselves should need to be loaded. Never used VE Damage Popup though. I think battles in general show the most improvement as they are the most likely to load and dispose things over and over. :3

Share this post


Link to post
Share on other sites

Hmm... I was wrong, it must be another script I had disabled that is causing the delays. By itself, there is no delay, and with this script, there is also no delay. So something else I have must be messing with it. Wish I knew what to compare to, to find the problem? Would the Cache module be responsible for drawing text in battle?

Share this post


Link to post
Share on other sites

No, drawing text has nothing to do with the cache... I can only assume your disposing something you shouldn't be somewhere. Here, try using this little script without my cache script and see what it's loading over and over:

 

module Cache

  def self.normal_bitmap(path)

    unless include?(path)

      puts("Loading: " + path)

      @cache[path] = Bitmap.new(path)

    end

    @cache[path]

  end

end

Edited by KilloZapit

Share this post


Link to post
Share on other sites

THIS DEFINITELY GREAT SCRIPT !!!

i try making my battle system like this.

victor animated battle + yanfly ace battle engine.

i disable the yanfly ace battle engine popups and slap moghunter pop up (the selling point of moghunter pop up over yanfly is because it can also used in maps not just battle)

 

but the problem is whenever i have skill that target lots of target in a time. the pop up will lagggg the games(noticeable). if i use yanfly pop up it doesn't lag (only slight bit. not noticeable). so i temporarily use the yanfly system pop up because of that problem.

 

now after trying this script (i renamed it Killozapit - SUPER CACHE script in my script list. because it's so SUPER! LOL)

i try again using moghunter damage pop up. and it really fasten the process. now the lag is not noticeable. :D. thx killozapit for this great script. you're a lifesaver.

 

i also have a suggestion. i guess you could add configuration to not print the puts.

so if that config set to true then print the puts. else don't.

 

and i also have question. so basically every image we want to keep (not disposed) added inside this method?

 

  def self.precache
    system("IconSet").keep_cached = true
    system("Window").keep_cached = true
    system("BattleStart").keep_cached = true
  end

 

and how we write it if example we put it in graphic/battlers/ folder.

does it like this?

 

battler("myhero1",0).keep_cached = true

edit: after some test that works.

this also works:

    load_bitmap("Graphics/System/","IconSet").keep_cached = true

Edited by estriole

Share this post


Link to post
Share on other sites
Pre Image cache is good, but people shouldn't overdo it or the result will be worse than if it wasn't being used.

Yeah that's one reason I only precached those three system images. The other "Cache Optimization" script just precaches everything! Though I am not sure exactly how much is too much... probably depends on how much memory is available.

Share this post


Link to post
Share on other sites
No, drawing text has nothing to do with the cache... I can only assume your disposing something you shouldn't be somewhere. Here, try using this little script without my cache script and see what it's loading over and over:

 

module Cache

  def self.normal_bitmap(path)

    unless include?(path)

      puts("Loading: " + path)

      @cache[path] = Bitmap.new(path)

    end

    @cache[path]

  end

end

 

Entered a battle and it output "Loading: Graphics/Battlers/Goblin (FF2)[anim]" about 2,000 times.

 

Which means it's loading the enemy battler graphics over and over and over and over every frame for no reason at all in VE Animated Battle. *sigh*

 

Any insight on what would cause that to happen, Victor? I don't even use battlers that have the [anim] suffix. Hoping this can be fixed so there's no more lag in battles when dealing damage to multiple targets.

Share this post


Link to post
Share on other sites

here this may help track it down:

 

 

module Error_Handler

 

  def self.do_backtrace(error)

    scripts_name = load_data('Data/Scripts.rvdata2')

    scripts_name.collect! {|script|  script[1]  }

    backtrace = []

    error.backtrace.each_with_index {|line,i|

      if line =~ /{(.*)}(.*)/

        backtrace << (scripts_name[$1.to_i] + $2)

      elsif line.start_with?(':1:')

        break

      else

        backtrace << line

      end

    }

    error_line = backtrace.first

    backtrace[0] = ''

    print error_line, ": ", error.message, " (#{error.class})", backtrace.join("\n\tfrom "), "\n"

    File.open('error.log', 'w') do |f|

      f.puts error_line, ": ", error.message, " (#{error.class})", backtrace.join("\n\tfrom "), "\n"

    end

  end

 

end

 

module Cache

  def self.normal_bitmap(path)

    unless include?(path)

      @cache[path] = Bitmap.new(path)

      if path == "Graphics/Battlers/Goblin (FF2)[anim]" @cache[path].testytest = true

    end

    @cache[path]

  end

end

 

class Bitmap

 

  attr_accessor :testytest

 

  alias_method :test_dispose, :dispose

  def dispose

    begin

      raise "test" if @testytest

    rescue Exception => error

      Error_Handler.do_backtrace(error)

    end

    test_dispose

  end

 

end

 

Share this post


Link to post
Share on other sites

It shouldn't be that hard to record some statistics on cache hits/misses and total time spend loading something I guess. Maybe even just pressing CTRL-ALT-DELETE to open the task manager and looking at the performance tab... Look to see how the system's swap memory, and maybe count page faults?

Share this post


Link to post
Share on other sites
No, drawing text has nothing to do with the cache... I can only assume your disposing something you shouldn't be somewhere. Here, try using this little script without my cache script and see what it's loading over and over:

 

module Cache

  def self.normal_bitmap(path)

    unless include?(path)

      puts("Loading: " + path)

      @cache[path] = Bitmap.new(path)

    end

    @cache[path]

  end

end

this is really helpful. now i can see what files that loaded a lot and then add that to precache. (of course not over precaching :D). i'm thinking to precache some frequently used animation to smooth the battle a little bit. (such as attack animation).

Share this post


Link to post
Share on other sites

Animation images can be pretty big you know... really you shouldn't really need to precache them, as with my script they should only need to load once per battle. Also, you could try setting BUFFER_DISPOSED_BITMAPS to false, and increasing BITMAP_MAX_AGE if you want stuff to stay in the cache longer, but I am not really sure of optimal settings yet.

 

Alternatively, you could do something like this:

 

class Scene_Battle < Scene_Base

  alias_method :cache_start_battle_base, :start

    def start

      Cache.animation("attack", 0) # no keep_cached here

      Cache.animation("attack2", 0)

      cache_start_battle_base

    end

end

Edited by KilloZapit

Share this post


Link to post
Share on other sites
Animation images can be pretty big you know... really you shouldn't really need to precache them, as with my script they should only need to load once per battle. Also, you could try setting BUFFER_DISPOSED_BITMAPS to false, and increasing BITMAP_MAX_AGE if you want stuff to stay in the cache longer, but I am not really sure of optimal settings yet.

 

Alternatively, you could do something like this:

 

class Scene_Battle < Scene_Base

  alias_method :cache_start_battle_base, :start

    def start

      Cache.animation("attack", 0) # no keep_cached here

      Cache.animation("attack2", 0)

      cache_start_battle_base

    end

end

aaahhh... this definitely better. so precache the 'common' used animation at start of battle. thx once again killo :D.

Share this post


Link to post
Share on other sites

I appolygize of this was asked and answered but does this lower the size of your game? I noticed RPG Maker VX Ace makes the files way too big when compressing encypted or nonencrypted.  I am just asking before I use this script because I cannot even add the rtp to my games when I upload them because it makes the file too big.  Also is this compatable with yanfly's save engine?

Share this post


Link to post
Share on other sites

Sorry, this script just effects how bitmaps are loaded and kept in memory. It has no effect on the size of the files themselves. It should be compatible with yanfly's save engine for sure, since it has no effect on anything besides graphics.

Share this post


Link to post
Share on other sites
here this may help track it down:

 

 

module Error_Handler

 

  def self.do_backtrace(error)

    scripts_name = load_data('Data/Scripts.rvdata2')

    scripts_name.collect! {|script|  script[1]  }

    backtrace = []

    error.backtrace.each_with_index {|line,i|

      if line =~ /{(.*)}(.*)/

        backtrace << (scripts_name[$1.to_i] + $2)

      elsif line.start_with?(':1:')

        break

      else

        backtrace << line

      end

    }

    error_line = backtrace.first

    backtrace[0] = ''

    print error_line, ": ", error.message, " (#{error.class})", backtrace.join("\n\tfrom "), "\n"

    File.open('error.log', 'w') do |f|

      f.puts error_line, ": ", error.message, " (#{error.class})", backtrace.join("\n\tfrom "), "\n"

    end

  end

 

end

 

module Cache

  def self.normal_bitmap(path)

    unless include?(path)

      @cache[path] = Bitmap.new(path)

      if path == "Graphics/Battlers/Goblin (FF2)[anim]" @cache[path].testytest = true

    end

    @cache[path]

  end

end

 

class Bitmap

 

  attr_accessor :testytest

 

  alias_method :test_dispose, :dispose

  def dispose

    begin

      raise "test" if @testytest

    rescue Exception => error

      Error_Handler.do_backtrace(error)

    end

    test_dispose

  end

 

end

 

post-9189-0-04557200-1357587253_thumb.png

 

That's what the script output to me. Any ideas?

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

  • Recently Browsing   0 members

    No registered users viewing this page.

×