class_name AssetCacheManager extends RefCounted ## Manages client-side disk caching of downloaded 3D model assets and collections. ## Caches assets locally under user://asset_cache/ to prevent duplicate network fetches. const CACHE_DIR: String = "user://asset_cache" static var _instance = null static func get_instance(): if _instance == null: var script := load("res://scripts/studio/asset_cache_manager.gd") _instance = script.new() return _instance func _init() -> void: _ensure_cache_dir() func _ensure_cache_dir() -> void: if not DirAccess.dir_exists_absolute(CACHE_DIR): DirAccess.make_dir_recursive_absolute(CACHE_DIR) func get_cache_path(asset_key: String) -> String: var clean_key := asset_key.replace("/", "_").replace("\\", "_").replace(":", "_") if not clean_key.ends_with(".glb") and not clean_key.ends_with(".gltf"): clean_key += ".glb" return "%s/%s" % [CACHE_DIR, clean_key] func has_cached(asset_key: String) -> bool: var p := get_cache_path(asset_key) return FileAccess.file_exists(p) func get_cached_path_if_exists(asset_key: String) -> String: var p := get_cache_path(asset_key) if FileAccess.file_exists(p): return p return "" func store_asset(asset_key: String, data: PackedByteArray) -> String: _ensure_cache_dir() var path := get_cache_path(asset_key) var fa := FileAccess.open(path, FileAccess.WRITE) if fa != null: fa.store_buffer(data) fa.close() return path return "" func read_asset(asset_key: String) -> PackedByteArray: var path := get_cache_path(asset_key) if not FileAccess.file_exists(path): return PackedByteArray() var fa := FileAccess.open(path, FileAccess.READ) if fa != null: var buf := fa.get_buffer(fa.get_length()) fa.close() return buf return PackedByteArray() func load_model(asset_key: String) -> Node: var path := get_cached_path_if_exists(asset_key) if path == "": return null if path.ends_with(".glb") or path.ends_with(".gltf"): var gltf := GLTFDocument.new() var state := GLTFState.new() var err := gltf.append_from_file(path, state) if err == OK: return gltf.generate_scene(state) elif ResourceLoader.exists(path): var res := load(path) if res is PackedScene: return (res as PackedScene).instantiate() return null func get_cached_items() -> Array[String]: _ensure_cache_dir() var items: Array[String] = [] var dir := DirAccess.open(CACHE_DIR) if dir != null: dir.list_dir_begin() var file_name := dir.get_next() while file_name != "": if not dir.current_is_dir() and not file_name.begins_with("."): items.append(file_name) file_name = dir.get_next() dir.list_dir_end() return items func clear_cache() -> int: _ensure_cache_dir() var count := 0 var dir := DirAccess.open(CACHE_DIR) if dir != null: dir.list_dir_begin() var file_name := dir.get_next() while file_name != "": if not dir.current_is_dir(): DirAccess.remove_absolute("%s/%s" % [CACHE_DIR, file_name]) count += 1 file_name = dir.get_next() dir.list_dir_end() return count