Is it possible to manually update/override entries in the ResourceLoader cache?

Godot Version

4.4

Question

tl;dr–is there a way to replace entries in the ResourceLoader cache at runtime such that any future load of that resource will use the updated version in the cache? In particular, I want this to work for resources that have been attached to a node or other resource in the editor via drag-n-drop, and which are therefore accessed by the containing node/resource via a “res://” path, e.g. res://path/to/res.tres.

I’ve tried storing a copy of the updated resource in the user folder at e.g. user://path/to/res.tres, and then calling various combinations of …

  • var loaded_resource = ResourceLoader.load("user://path/to/res.tres", "", ResourceLoader.CACHE_MODE_REPLACE_DEEP)
  • var loaded_resource = ResourceLoader.load("res://path/to/res.tres", "", ResourceLoader.CACHE_MODE_REPLACE_DEEP)
  • loaded_resource.take_over_path("user://path/to/res.tres")
  • loaded_resource.take_over_path("res://path/to/res.tres")
  • loaded_resource.resource_path ="user://path/to/res.tres"
  • loaded_resource.resource_path ="res://path/to/res.tres"

… to see if there is some way of loading the updated file from the user folder and replacing the corresponding key in the ResourceLoader cache, but I haven’t found the right combination, and the docs are somewhat terse on this obscure topic, so I’m not even sure if this is possible.

Many thanks for any tips!!

My use case:
I’m aiming for a Workflow Nirvana that will satisfy a few objectives at once.
Basically, I have a bunch of nested resources that define gameplay parameters in res://params, and I want to accomplish the following:

Objective 1)
Maintain type safety. My resources are defined such that any required (sub)resources are given an explicit type. I.e., if the resource EnemyParams requires an EnemyBrainParams resource, then in the EnemyParams resource I use @export var brain : EnemyBrainParams to make this explicit

Objective 2)
For playtesting, I want to be able to push updated version of these resource files over the wire and have these updated resources be used the next time a copy of of the resource needs to be loaded. This doesn’t need to be “hot reloading”, in fact the ideal scenario will be to push tweaked sets of gameplay parameters between playtest runs. But I want to be able to do this without having to push a build etc; I just want to tell users to drop out to the main menu, I want to push some changes on my end, and I want the changes to the resources to be visible when they start another play session.

Objective 3)
Work with resource refs, not path strings. Avoid manually calling load(...) within each resource script. Too boilerplatey, undermines type safety, undermines editor convenience features like curated quick loads.

If I had one single load+cache override script that read an updated batch of params from the user folder, and replaced the cache entry for every resource previously loaded from the res folder, then I’d be all set. Is that possible?

I don’t think what you are asking is possible.

The only thing that could work is to use Resource.take_over_path() in the loaded resource with the original resource path but that removes the original loaded resource cached path godot/core/io/resource.cpp at 0028fd625e2c5b202e204bc12828cbca043213d3 · godotengine/godot · GitHub

Not sure why, though.

func take_over() -> void:
	var a = load("res://my_resource.tres")
	printt("a", a)
	print("duplicate, change, and take_over b")
	var b = a.duplicate(true)
	b.my_int = randi()
	b.my_string = Marshalls.variant_to_base64(randi())
	b.take_over_path("res://my_resource.tres")
	printt("a", a)
	printt("b", b)
	print("load resource in c")
	var c = load("res://my_resource.tres")
	printt("a", a)
	printt("b", b)
	printt("c", c)

Prints:

a	My int << 2049117313 >> My String << AgABAD9iD60AAAAA >> My Resource Int << 100 >> Path -> res://my_resource.tres
duplicate, change, and take_over b
a	My int << 2049117313 >> My String << AgABAD9iD60AAAAA >> My Resource Int << 100 >> Path -> 
b	My int << 2150833802 >> My String << AgABAIR2IPUAAAAA >> My Resource Int << 100 >> Path -> res://my_resource.tres
load resource in c
a	My int << 2049117313 >> My String << AgABAD9iD60AAAAA >> My Resource Int << 100 >> Path -> 
b	My int << 2150833802 >> My String << AgABAIR2IPUAAAAA >> My Resource Int << 100 >> Path -> res://my_resource.tres
c	My int << 2150833802 >> My String << AgABAIR2IPUAAAAA >> My Resource Int << 100 >> Path -> res://my_resource.tres

Notice how a loses the resource_path after b takes over that path. Loading the resource from that path again (c) uses b instead of the original saved resource (a had the original one until b took over the path)