diff --git a/classes/deck/renderer_persistence.gd b/classes/deck/renderer_persistence.gd new file mode 100644 index 0000000..3b5e6ae --- /dev/null +++ b/classes/deck/renderer_persistence.gd @@ -0,0 +1,137 @@ +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: + print("Could not create namespace: error %s" % err) + 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) -> 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): + print("Namespace %s is not initialized" % name_space) + 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))