Kayzee 3,815 Posted January 4, 2013 (edited) 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 June 14, 2015 by KilloZapit 9 Neok, Wren, Archeia and 6 others reacted to this Share this post Link to post Share on other sites
Ocedic 249 Posted January 4, 2013 Pardon my ignorance, but to clarify by default does the game dispose and load images into the cache eery scene change/map transition? Share this post Link to post Share on other sites
Kayzee 3,815 Posted January 4, 2013 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
Ocedic 249 Posted January 4, 2013 (edited) 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 January 4, 2013 by Ocedic Share this post Link to post Share on other sites
Kayzee 3,815 Posted January 4, 2013 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
Coolie 147 Posted January 4, 2013 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
Kayzee 3,815 Posted January 4, 2013 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
Coolie 147 Posted January 4, 2013 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
Kayzee 3,815 Posted January 5, 2013 (edited) 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 January 5, 2013 by KilloZapit Share this post Link to post Share on other sites
estriole 326 Posted January 5, 2013 (edited) 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. . 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 January 5, 2013 by estriole Share this post Link to post Share on other sites
Victor Sant 273 Posted January 5, 2013 @Willian C Maybe it was this script. the RM draw_text is slow as hell. Pre Image cache is good, but people shouldn't overdo it or the result will be worse than if it wasn't being used. Share this post Link to post Share on other sites
estriole 326 Posted January 5, 2013 @victor: thanks. i also looking for that . Share this post Link to post Share on other sites
Kayzee 3,815 Posted January 5, 2013 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
Coolie 147 Posted January 5, 2013 Great, I'll give those a try after work today. Thanks Victor and Killo. Share this post Link to post Share on other sites
Coolie 147 Posted January 6, 2013 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
Kayzee 3,815 Posted January 6, 2013 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
Tsukihime 1,487 Posted January 6, 2013 Any idea how this could be benchmarked? Share this post Link to post Share on other sites
Kayzee 3,815 Posted January 7, 2013 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
estriole 326 Posted January 7, 2013 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 ). 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
Kayzee 3,815 Posted January 7, 2013 (edited) 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 January 7, 2013 by KilloZapit Share this post Link to post Share on other sites
estriole 326 Posted January 7, 2013 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 . Share this post Link to post Share on other sites
Kayzee 3,815 Posted January 7, 2013 Hehe... you are welcome! I like to help! :3 Share this post Link to post Share on other sites
RoseGuardian 42 Posted January 7, 2013 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
Kayzee 3,815 Posted January 7, 2013 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
Coolie 147 Posted January 7, 2013 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 That's what the script output to me. Any ideas? Share this post Link to post Share on other sites