Compare commits

...

2 Commits

Author SHA1 Message Date
b5fe707c1a Merge branch 'main' of dev.dinoleaf.com:pyral/GD-VirtualFileAccess 2024-12-03 13:44:35 -05:00
88757cf007 Fixed stuff 2024-12-03 13:43:48 -05:00
4 changed files with 97 additions and 15 deletions

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Dinoleaf LLC
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,5 +1,8 @@
# Not unlike factory pattern. # Not unlike factory pattern.
# This code's been directly written in due to time and lazy, but
# will be thrown into submodules eventually to clean readability up.
## Recursively creates virtual file systems. ## Recursively creates virtual file systems.
static func create_meta_accessor(file_accessors:Array[VFileAccess])->VFileAccess: static func create_meta_accessor(file_accessors:Array[VFileAccess])->VFileAccess:
var vfiler := VFileAccess.new() var vfiler := VFileAccess.new()
@ -39,8 +42,11 @@ static func create_meta_accessor(file_accessors:Array[VFileAccess])->VFileAccess
## A basic file accessor. ## A basic file accessor.
static func create_file_access(root:String = "./")->VFileAccess: static func create_file_access(
var vfiler := VFileAccess.new(root) root:String = "./",
support_files:Dictionary[String,Callable] = VFileAccess.IMPORTS.DEFAULT_SUPPORTED_FILES
)->VFileAccess:
var vfiler := VFileAccess.new(root, support_files)
vfiler._get_buffer = FileAccess.get_file_as_bytes # thanks for being static vfiler._get_buffer = FileAccess.get_file_as_bytes # thanks for being static
@ -68,7 +74,7 @@ static func create_file_access(root:String = "./")->VFileAccess:
static func create_writeonly_zip_access( static func create_writeonly_zip_access(
zip_path:String, zip_path:String,
append := ZIPPacker.ZipAppend.APPEND_CREATE append := ZIPPacker.ZipAppend.APPEND_CREATE,
)->VFileAccess: )->VFileAccess:
var vfiler := VFileAccess.new() var vfiler := VFileAccess.new()
if not is_instance_valid(vfiler): return null if not is_instance_valid(vfiler): return null
@ -106,18 +112,23 @@ static func create_writeonly_zip_access(
## A single zip file accessor, but with write functions disabled. ## A single zip file accessor, but with write functions disabled.
## TODO option to keep zips open, for OS to flag files as in-use. ## TODO option to keep zips open, for OS to flag files as in-use.
static func create_readonly_zip_access(zip_path:String)->VFileAccess: static func create_readonly_zip_access(
var vfiler := create_bulk_readonly_zip_access([zip_path]) zip_path:String, keep_open:bool = false,
support_files:Dictionary[String,Callable] = VFileAccess.IMPORTS.DEFAULT_SUPPORTED_FILES
)->VFileAccess:
var vfiler := create_bulk_readonly_zip_access([zip_path], keep_open, support_files)
vfiler._get_stuff = func()->Variant: return vfiler.get_stuff()[0] vfiler._get_stuff = func()->Variant: return vfiler.get_stuff()[0]
return vfiler return vfiler
## A multi-zip readonly accessor. Allows for multiple zips to load with overrides. ## A multi-zip readonly accessor. Allows for multiple zips to load with overrides.
## TODO option to keep zips open, for OS to flag files as in-use. ## TODO option to keep zips open, for OS to flag files as in-use.
## [param if_missing_zip(String)] executes if a requested file is not found.
static func create_bulk_readonly_zip_access( static func create_bulk_readonly_zip_access(
zip_paths:Array[String], keep_open:bool = false, zip_paths:Array[String], keep_open:bool = false,
if_missing_zip := func _ignore(_zip_path:String)->bool: return false support_files:Dictionary[String,Callable] = VFileAccess.IMPORTS.DEFAULT_SUPPORTED_FILES,
if_missing_zip := func _ignore(_zip_path:String)->bool: return false,
)->VFileAccess: )->VFileAccess:
var vfiler := VFileAccess.new("") var vfiler := VFileAccess.new("", support_files)
var readers:Array[ZIPReader] = [] var readers:Array[ZIPReader] = []
readers.assign(zip_paths.map( readers.assign(zip_paths.map(

37
vfs.gd
View File

@ -2,6 +2,8 @@
## ##
## File access across different methods sucks. ## File access across different methods sucks.
## Here's something to abstract that away and suck a little less. ## Here's something to abstract that away and suck a little less.
##
## Written for Godot 4.4
class_name VFileAccess extends RefCounted class_name VFileAccess extends RefCounted
@ -14,24 +16,40 @@ const IMPORTS:GDScript = preload("vfs_loaders.gd")
var root:String = "" var root:String = ""
## Callable(bytes:PackedByteArray, [...])->Variant ## Callable(bytes:PackedByteArray, [...])->Variant
var supported_files:Dictionary[String,Callable] = IMPORTS.DEFAULT_SUPPORTED_FILES var supported_files:Dictionary[String,Callable] = {}
## Get any stuff we might be using in our closures. ## Get any stuff we might be using in our closures.
var _get_stuff:Callable = func()->Variant: return null var _get_stuff:Callable = func()->Variant: return null
## How can we write to a file? ## How can we write to a file?
var _write_file:Callable = func(_abs_path:String, data:Variant)->Error: return ERR_CANT_OPEN var _write_file:Callable = func(_abs_path:String, data:Variant)->Error:
return ERR_CANT_OPEN
## How do we get bytes from this? ## How do we get bytes from this?
var _get_buffer:Callable = func(_abs_path:String)->PackedByteArray: return PackedByteArray() var _get_buffer:Callable = func(_abs_path:String)->PackedByteArray:
return PackedByteArray()
## What determines if a file exists? ## What determines if a file exists?
var _file_exists:Callable = func(_abs_path:String)->bool: return false var _file_exists:Callable = func(_abs_path:String)->bool:
return false
## How do we get files at a subdirectory? ## How do we get files at a subdirectory?
var _get_files_at:Callable = func(_abs_path:String)->Array[String]: return [] var _get_files_at:Callable = func(_abs_path:String)->Array[String]:
return []
## Shutdown code here. ## Shutdown code here.
var _close:Callable = func()->void: pass var _close:Callable = func()->void: pass
## Default ## Default
func _init(root_dir:String = "./")->void: func _init(
root_dir:String = "./",
support_files = IMPORTS.DEFAULT_SUPPORTED_FILES
)->void:
self.root = root_dir self.root = root_dir
self.supported_files = support_files.duplicate(true)
#region Static ops
static func copy_file(from:VFileAccess, to:VFileAccess)->bool:
push_warning("Copy file unimplemented")
return false
#endregion
## I never won awards for naming things correctly. ## I never won awards for naming things correctly.
func get_stuff()->Variant: func get_stuff()->Variant:
@ -54,6 +72,13 @@ func get_supported_files()->Array[String]:
return supported_files.keys() return supported_files.keys()
func load_supported_bulk(
paths:Array[String],
ext_override:String = ""
)->Array[Variant]:
return paths.map(load_supported.bind(ext_override))
## Load a supported file. If [param path]'s extension matches a supported file, ## Load a supported file. If [param path]'s extension matches a supported file,
## it will use that loader and return whatever it's supposed to. ## it will use that loader and return whatever it's supposed to.
## [param ext_override] allows for selecting a specific loader by key. ## [param ext_override] allows for selecting a specific loader by key.

View File

@ -14,10 +14,15 @@ static func validate_loader(loader:Callable)->bool:
static func load_bin(buffer:PackedByteArray)->PackedByteArray: return buffer static func load_bin(buffer:PackedByteArray)->PackedByteArray: return buffer
static func load_png(buffer:PackedByteArray)->Image: static func load_png(buffer:PackedByteArray)->Image:
var img:Image var img := Image.new()
img.load_png_from_buffer(buffer) img.load_png_from_buffer(buffer)
return img return img
static func load_jpg(buffer:PackedByteArray)->Image:
var img := Image.new()
img.load_jpg_from_buffer(buffer)
return img
static func load_mp3(buffer:PackedByteArray)->AudioStreamMP3: static func load_mp3(buffer:PackedByteArray)->AudioStreamMP3:
var sfx := AudioStreamMP3.new() var sfx := AudioStreamMP3.new()
sfx.data = buffer sfx.data = buffer
@ -32,19 +37,39 @@ static func load_txt(buffer:PackedByteArray)->String:
var txt:String = buffer.get_string_from_utf8() var txt:String = buffer.get_string_from_utf8()
return txt return txt
static func load_wav(buffer:PackedByteArray)->AudioStreamWAV:
var sfx := AudioStreamWAV.new()
push_warning("""
Cannot automatically parse wav because dynamic Godot parsing wav is currently manual and awful.
Despite this, somehow Godot's editor importer seems to handle parsing wav just fine.""")
return sfx
# can't use CONST since Callables are technically instanced dynamically # can't use CONST since Callables are technically instanced dynamically
static var DEFAULT_SUPPORTED_FILES:Dictionary[String,Callable] = { static var DEFAULT_SUPPORTED_FILES:Dictionary[String,Callable] = {
"bin": load_bin, "bin": load_bin,
"txt": load_txt,
"png": load_png, "png": load_png,
"jpg": load_jpg,
"jpeg": load_jpg,
"mp3": load_mp3, "mp3": load_mp3,
"ogg": load_ogg, "ogg": load_ogg,
"wav": load_wav,
}
static var DATA_FILES:Dictionary[String,Callable] = {
"bin": load_bin,
"txt": load_txt, "txt": load_txt,
} }
static var IMAGE_FILES:Dictionary[String,Callable] = {
"png": load_png,
"jpg": load_jpg,
"jpeg": load_jpg,
}
static var AUDIO_FILES:Dictionary[String,Callable] = { static var AUDIO_FILES:Dictionary[String,Callable] = {
"mp3": load_mp3, "mp3": load_mp3,
"ogg": load_ogg, "ogg": load_ogg,
"wav": load_wav,
} }