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.
# 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.
static func create_meta_accessor(file_accessors:Array[VFileAccess])->VFileAccess:
var vfiler := VFileAccess.new()
@ -39,8 +42,11 @@ static func create_meta_accessor(file_accessors:Array[VFileAccess])->VFileAccess
## A basic file accessor.
static func create_file_access(root:String = "./")->VFileAccess:
var vfiler := VFileAccess.new(root)
static func create_file_access(
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
@ -68,7 +74,7 @@ static func create_file_access(root:String = "./")->VFileAccess:
static func create_writeonly_zip_access(
zip_path:String,
append := ZIPPacker.ZipAppend.APPEND_CREATE
append := ZIPPacker.ZipAppend.APPEND_CREATE,
)->VFileAccess:
var vfiler := VFileAccess.new()
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.
## TODO option to keep zips open, for OS to flag files as in-use.
static func create_readonly_zip_access(zip_path:String)->VFileAccess:
var vfiler := create_bulk_readonly_zip_access([zip_path])
static func create_readonly_zip_access(
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]
return vfiler
## 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.
## [param if_missing_zip(String)] executes if a requested file is not found.
static func create_bulk_readonly_zip_access(
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:
var vfiler := VFileAccess.new("")
var vfiler := VFileAccess.new("", support_files)
var readers:Array[ZIPReader] = []
readers.assign(zip_paths.map(

37
vfs.gd
View File

@ -2,6 +2,8 @@
##
## File access across different methods sucks.
## Here's something to abstract that away and suck a little less.
##
## Written for Godot 4.4
class_name VFileAccess extends RefCounted
@ -14,24 +16,40 @@ const IMPORTS:GDScript = preload("vfs_loaders.gd")
var root:String = ""
## 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.
var _get_stuff:Callable = func()->Variant: return null
## 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?
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?
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?
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.
var _close:Callable = func()->void: pass
## Default
func _init(root_dir:String = "./")->void:
func _init(
root_dir:String = "./",
support_files = IMPORTS.DEFAULT_SUPPORTED_FILES
)->void:
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.
func get_stuff()->Variant:
@ -54,6 +72,13 @@ func get_supported_files()->Array[String]:
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,
## it will use that loader and return whatever it's supposed to.
## [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_png(buffer:PackedByteArray)->Image:
var img:Image
var img := Image.new()
img.load_png_from_buffer(buffer)
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:
var sfx := AudioStreamMP3.new()
sfx.data = buffer
@ -32,19 +37,39 @@ static func load_txt(buffer:PackedByteArray)->String:
var txt:String = buffer.get_string_from_utf8()
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
static var DEFAULT_SUPPORTED_FILES:Dictionary[String,Callable] = {
"bin": load_bin,
"txt": load_txt,
"png": load_png,
"jpg": load_jpg,
"jpeg": load_jpg,
"mp3": load_mp3,
"ogg": load_ogg,
"wav": load_wav,
}
static var DATA_FILES:Dictionary[String,Callable] = {
"bin": load_bin,
"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] = {
"mp3": load_mp3,
"ogg": load_ogg,
"wav": load_wav,
}