# (c) 2023-present Eroax # (c) 2023-present Yagich # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) class_name RendererPersistence ## An interface for per-renderer persistent data. ## ## Allows renderers to store key/value data, split into namespaces (usually the renderer name) ## and channels (files on a filesystem). For every namespace, a folder is created, ## and populated with JSON files named after the channel name. const _BASE_PATH := "user://renderer_persistence/" const _FILE_TEMPLATE := "{namespace}/{channel}.json" # Dictionary[String -> namespace, Dictionary[String -> channel, Variant]] static var _data: Dictionary #region API ## Initializes a namespace. Returns [code]false[/code] if the folder for the namespace didn't exist ## before, useful for initializing defaults. static func init_namespace(name_space: String) -> bool: var n := name_space.validate_filename() if _data.get(n) != null: return true var err := _create_namespace_folder(n) if err != OK: DeckHolder.logger.log_system("Could not create namespace: error %s" % err, Logger.LogType.ERROR) return false _data[name_space] = {} return false ## Returns the corresponding value for the given [param key] in the [param channel] storage. ## If the key does not exist, returns [param default], or [code]null[/code] if the parameter is omitted. static func get_value(name_space: String, channel: String, key: String, default: Variant = null) -> Variant: _lazy_load_channel(name_space, channel) var validated_name := name_space.validate_filename() var validated_channel := channel.validate_filename() return (_data[validated_name][validated_channel] as Dictionary).get(key, default) ## Sets the value in the [param channel] storage at [param key] to [param value]. static func set_value(name_space: String, channel: String, key: String, value: Variant) -> void: _lazy_load_channel(name_space, channel) var validated_name := name_space.validate_filename() var validated_channel := channel.validate_filename() _data[validated_name][validated_channel][key] = value ## Returns the corresponding value for the given [param key] in the [param channel] storage. ## If the key does not exist, creates it in the storage, initializes it to [param default] ## and returns it. static func get_or_create(name_space: String, channel: String, key: String, default: Variant) -> Variant: _lazy_load_channel(name_space, channel) var validated_name := name_space.validate_filename() var validated_channel := channel.validate_filename() var channel_data: Dictionary = _data[validated_name][validated_channel] return _get_or_create(channel_data, key, default) ## Erases an entry in the store by [param key] and returns its previous value, ## or [code]null[/code] if it didn't exist. static func erase(name_space: String, channel: String, key: String) -> Variant: _lazy_load_channel(name_space, channel) var validated_name := name_space.validate_filename() var validated_channel := channel.validate_filename() var channel_data: Dictionary = _data[validated_name][validated_channel] var ret = channel_data.get(key) channel_data.erase(key) return ret ## Commits the [param channel] to the filesystem. If [param channel] is empty, ## commits all channels in the [param name_space]. static func commit(name_space: String, channel: String = "") -> void: if !channel.is_empty(): _commit_channel(name_space, channel) else: for c: String in _data[name_space]: _commit_channel(name_space, c) #endregion static func _get_or_create(d: Dictionary, key: String, default: Variant) -> Variant: var r = d.get(key) if r == null: r = default d[key] = r return r static func _create_namespace_folder(name_space: String) -> Error: return DirAccess.make_dir_recursive_absolute(_BASE_PATH.path_join(name_space)) static func _lazy_load_channel(name_space: String, channel: String) -> void: var validated_name := name_space.validate_filename() var validated_channel := channel.validate_filename() if !_data.has(validated_name): DeckHolder.logger.log_system("Namespace %s is not initialized" % name_space, Logger.LogType.ERROR) return if (_data[validated_name] as Dictionary).has(validated_channel): return var path := _BASE_PATH.path_join(_FILE_TEMPLATE.format( { "namespace": validated_name, "channel": validated_channel })) var f := FileAccess.open(path, FileAccess.READ) if f == null: _data[validated_name][validated_channel] = {} return var data = JSON.parse_string(f.get_as_text()) _data[validated_name][validated_channel] = data static func _commit_channel(name_space: String, channel: String) -> void: _lazy_load_channel(name_space, channel) var validated_name := name_space.validate_filename() var validated_channel := channel.validate_filename() var path := _BASE_PATH.path_join(_FILE_TEMPLATE.format( { "namespace": validated_name, "channel": validated_channel })) var f := FileAccess.open(path, FileAccess.WRITE) f.store_string(JSON.stringify(_data[validated_name][validated_channel], "\t", false))