Skip to content

Instantly share code, notes, and snippets.

@YuriSizov
Last active April 15, 2024 10:29
Show Gist options
  • Save YuriSizov/6fc5da7b9fedb7094d4bd9e41c7b44b3 to your computer and use it in GitHub Desktop.
Save YuriSizov/6fc5da7b9fedb7094d4bd9e41c7b44b3 to your computer and use it in GitHub Desktop.
Busting Godot resource cache

Godot's resource caching can cause a lot of issues when working on tools for your games.

Editor, when running, assumes it has an authority on resources and ignores changes done to your custom resources externally. "Externally" here doesn't even mean "outside of Godot editor". It just means "outside of Godot editor's own resource saving logic".

On top of that, ResourceLoader.load is untrustworthy when it comes to ignoring cache. While the main loaded resource will respect the nocache argument and will be returned fresh, its subresources are not so lucky.

To fight that I have a truly fresh resource loader util method, that copies the file before loading to a randomly named path, loads it from there and then takes over the old path. That ensures for your plugins and running scenes that the resource is truly loaded from the disk.

However, editor is not so impressed with these efforts normally. To fight that, I had to create a tiny plugin. It can be adapted to other use cases, but for my current project I needed it to force rescan resources after I run a custom scene. So that's what it does. And then, when the resource is reloaded, it resaves it for good measure using the previously described utility method.

tool
extends EditorPlugin
var force_rescan: bool = false
func _enter_tree() -> void:
get_editor_interface().get_resource_filesystem().connect("resources_reload", self, "_on_resources_reloaded")
func _exit_tree() -> void:
get_editor_interface().get_resource_filesystem().disconnect("resources_reload", self, "_on_resources_reloaded")
func _process(delta: float) -> void:
if !force_rescan && get_editor_interface().is_playing_scene():
print("Will rescan the file system after project run.")
force_rescan = true
elif force_rescan && !get_editor_interface().is_playing_scene():
force_rescan = false
print("Rescanning the file system after project run...")
get_editor_interface().get_resource_filesystem().scan()
func _on_resources_reloaded(resource_list: PoolStringArray) -> void:
print("Re-saving externally changed resources to bust the cache...")
for resource_path in resource_list:
print(".. Busting '%s'" % [ resource_path ])
ResourceUtils.load_fresh(resource_path)
extends Object
class_name ResourceUtils
# ResourceLoader is unreliable when it comes to cache.
# Sub-resources get cached regardless of the argument passed to load function.
# This is a workaround that generates a new file on a fly,
# while making sure that there is no cache record for it.
# This file is then used to load the resource, after which
# the resource takes over the original path.
static func load_fresh(resource_path : String) -> Resource:
var resource = File.new()
var error = resource.open(resource_path, File.READ)
if (error != OK):
printerr("Failed to load resource '" + resource_path + "': Error code " + str(error))
return null
var resource_ext = resource_path.get_extension()
var random_index = randi()
var intermediate_path = resource_path + "_temp_" + str(random_index) + "." + resource_ext
while (ResourceLoader.has_cached(intermediate_path)):
random_index = randi()
intermediate_path = resource_path + "_temp_" + str(random_index) + "." + resource_ext
var intermediate_resource = File.new()
error = intermediate_resource.open(intermediate_path, File.WRITE)
if (error != OK):
printerr("Failed to load resource '" + resource_path + "': Error code " + str(error))
return null
var resource_content = resource.get_as_text()
intermediate_resource.store_string(resource_content)
intermediate_resource.close()
resource.close()
var actual_resource = ResourceLoader.load(intermediate_path, "", true)
actual_resource.take_over_path(resource_path)
var directory = Directory.new()
directory.remove(intermediate_path)
return actual_resource
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment