How to reload/update files at runtime?

Godot Version

4.3

Question

I am trying to replicate this, using gdscript https://www.reddit.com/r/godot/comments/1d3cxan/runtime_editing_of_temporary_character_textures/

The goal is to edit a texture while the game is running and then reload it.

So I’ve tried something like this:

texture = ImageTexture.create_from_image(Image.load_from_file("res://file.png"))

And this:

texture = load("res://file.png")

But it will only load the texture the way it was when I hit play whenever I try to update it; it doesn’t load the edited image (it’s like the file is cached as it was when it was originally loaded and whenever I try to re-load it it’s loaded from cache instead of from the actual file)

The editor itself seems to update the file whenever I begin and end a playtest.

How do I reload a file at runtime?

They probably aren’t using res:// paths, instead system or user:// paths.

Firstly, use user:// for editing images while running.

Secondly this gets messy due to file locking which does not seem to be gracefully handled.

For example, the below script I just hashed up ‘works’, but I can’t seem to catch the C Errors it prints to console due to trying to load a partially complete image.

Hopefully a good place to start though:

var filepath := "user://picture.png"
var filesize:=0


func _physics_process(_delta):
	if FileAccess.file_exists(filepath):
		var file = FileAccess.open(filepath, FileAccess.READ)
		if file and file.get_open_error() == OK:
			var modifiedsize = FileAccess.get_modified_time(filepath)
			if filesize != modifiedsize:
				print("File size changed, attempting to load image...")
				if file and file.get_open_error() == OK:
					var buffer = file.get_buffer(file.get_length())
					if buffer.size() > 0:
						var image = Image.new()
						var error = image.load_png_from_buffer(buffer) # Arbitrarily seems to fail with uncaught C errors. Feels like a bug tbh
						if error == OK and image.is_empty() == false:
							print("Image loaded and valid")
							$TextureRect.texture = ImageTexture.create_from_image(image)
							filesize = modifiedsize
						else:
							print("Error: Image is not valid or could not be loaded.")
					file.close()
				else:
					print("File is currently being written to or inaccessible.")

and how it looks:

I mean why is Image.is_valid() not exposed to gdscript >.>

Wait my code was fine, i was just screwing up because of a bad if statement :man_facepalming:

Man, I spent so long overlooking it.

I basically just had to change if texture.resource_path == BMP to

var imgpath: String = texture.resource_path
...
...
if imgpath == BMP:

Because apparently the texture.resource_path changes when I reload the image, even if it shouldn’t cuz it’s the same image in the same location.

I’m just gonna accept ur answer though cuz it was fairly good.

Your error is probably because you’re running it on every physics frame, so therefore it is running while the image is being saved, when it is being saved the image file may be incomplete for some milliseconds and thus it will read as corrupt.

Some discoveries:

load("path") only works for files under res://, it doesn’t work for files under user://

1 Like

How did you resolve the file locking/corrupt image loading?

I’m not running it on every frame, i’m actually editing the image with godot so I just use a signal.

My whole code

A way you could solve it though is to after

if filesize != modifiedsize:

Use a timer to wait an arbitrary amount of time (maybe 2 seconds) before attempting to change the image.

But your current approach is fine imo. You get an error, but you handle it just fine, I’d say just don’t let it bother you (maybe see if there’s a way to suppress errors for this particular code). I’d be more worried about streamlining the code cuz those are some deeply nested if statements right there :grimacing: so many validity checks, and most of them not cheap.

Oh I don’t need to solve it, I wrote that code purely to answer this post because I thought it would be fun.

The timer is not going to prevent the error though, it would just make it occur less frequently and make the third party app to Godot refresh slower. The nested IFs were me trying to get to the bottom of the error due to simultaneous file access (i.e. trying to load the image in Godot before GIMP has finished saving it).

I’ve logged a github issue as I think it’s actually a bug in how it’s being handled in core, that is var error = image.load_png_from_buffer(buffer) should NOT cause a C++ stderr under any condition which is what seems to be happening.

I see yeah it really shouldn’t be doing that.

I really wonder if there’s a way to make this code work with texture.load() though.

The reason I’m trying to do it this way is becase I have some code to re-scale SVG images, and I wanted to try to optimize it by storing the scaled images in a cached PNGs or BMPs, then loading the textures from those cached files in the hopes that if the textures are being loaded from the same file they would share memory space (to prevent duplicate memory storage of identical textures) as opposed to from buffers which i’m pretty sure is creating unique instances of each ‘image created from buffer’ even if they are identical. I could be wrong though, I’m making a lot of assumptions.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.