diff options
-rw-r--r-- | ext/persist.lv2/persist.h | 50 | ||||
-rw-r--r-- | ext/persist.lv2/persist.ttl | 123 |
2 files changed, 120 insertions, 53 deletions
diff --git a/ext/persist.lv2/persist.h b/ext/persist.lv2/persist.h index 928a297..2ac4880 100644 --- a/ext/persist.lv2/persist.h +++ b/ext/persist.lv2/persist.h @@ -29,26 +29,31 @@ extern "C" { #define LV2_PERSIST_URI "http://lv2plug.in/ns/ext/persist" -/** Causes the host to store a value under a given key. - * - * This callback is passed by the host to LV2_Persist.save(). +/** A host-provided function to store a value under a given key. * @param callback_data Must be the callback_data passed to LV2_Persist.save(). - * @param key The URI key (RDF predicate) under which the value is to be stored. - * @param value Pointer to the value (RDF object) to be stored. + * @param key The URI key (predicate) under which the value is to be stored. + * @param value Pointer to the value (object) to be stored. * @param size The size of the data at @a value in bytes. * @param type The type of @a value, as a URI mapped to an integer. * + * The host passes a callback of this type to LV2_Persist.save(). This + * callback is called repeatedly by the plugin within LV2_Persist.save() to + * store all the key/value records that describe its current state. + * * Unless @a type is 0, @a value is guaranteed to be POD (i.e. a region * of memory that does not contain pointers and can safely be copied * and persisted indefinitely with a simple memcpy). If @a type is 0, * then @a value is a reference, as defined by the LV2 Atom extension * <http://lv2plug.in/ns/ext/atom/>. Hosts are not required to support - * references, a plugin MUST NOT expect a host to persist references unless + * references: a plugin MUST NOT expect a host to persist references unless * the host supports the feature <http://lv2plug.in/ns/ext/atom#blobSupport>. + * Plugins SHOULD express their state entirely with POD values. * * Note that @a size MUST be > 0, and @a value MUST point to a valid region of * memory @a size bytes long (this is required to make restore unambiguous). - * If only the key is of interest, store the empty string (which has size 1). + * + * The plugin MUST NOT attempt to use this function outside of the + * LV2_Persist.restore() context. */ typedef void (*LV2_Persist_Store_Function)( void* callback_data, @@ -57,19 +62,23 @@ typedef void (*LV2_Persist_Store_Function)( size_t size, uint32_t type); -/** Causes the host to retrieve a value under a given key. - * - * This callback is passed by the host to LV2_Persist.restore(). +/** A host-provided function to retrieve a value under a given key. * @param callback_data Must be the callback_data passed to LV2_Persist.restore(). - * @param key The URI key (RDF predicate) under which a value has been stored. + * @param key The URI key (predicate) under which a value has been stored. * @param size (Output) If non-NULL, set to the size of the restored value. * @param type (Output) If non-NULL, set to the type of the restored value. - * @return A pointer to the restored value (RDF object), or NULL if no value + * @return A pointer to the restored value (object), or NULL if no value * has been stored under @a key. * - * The returned value MUST remain valid until LV2_Persist.restore() returns. The plugin - * MUST NOT attempt to access a returned pointer outside of the LV2_Persist.restore() - * context (it MUST make a copy in order to do so). + * A callback of this type is passed by the host to LV2_Persist.restore(). This + * callback is called repeatedly by the plugin within LV2_Persist.restore() to + * retrieve the values of any keys it requires to restore its state. + * + * The returned value MUST remain valid until LV2_Persist.restore() returns. + * + * The plugin MUST NOT attempt to use this function, or any value returned from + * it, outside of the LV2_Persist.restore() context. Returned values MAY be + * copied for later use if necessary. */ typedef const void* (*LV2_Persist_Retrieve_Function)( void* callback_data, @@ -98,8 +107,8 @@ typedef const void* (*LV2_Persist_Retrieve_Function)( * with meaningful types to avoid such compatibility issues in the future. */ typedef struct _LV2_Persist { - /** Causes the plugin to save state data using a host-provided - * @a store callback. + + /** Save plugin state using a host-provided @a store callback. * * @param instance The instance handle of the plugin. * @param store The host-provided store callback. @@ -131,18 +140,17 @@ typedef struct _LV2_Persist { * Plugins that dynamically modify state while running, however, * must take care to do so in such a way that a concurrent call to * save() will save a consistent representation of plugin state for a - * single point in time. The simplest way to do this is to modify a + * single instant in time. The simplest way to do this is to modify a * copy of the state map and atomically swap a pointer to the entire * map once the changes are complete (for very large state maps, - * a purely functional map data structure would be more appropriate + * a purely functional map data structure may be more appropriate * since a complete copy is not necessary). */ void (*save)(LV2_Handle instance, LV2_Persist_Store_Function store, void* callback_data); - /** Causes the plugin to restore state data using a host-provided - * @a retrieve callback. + /** Restore plugin state using a host-provided @a retrieve callback. * * @param instance The instance handle of the plugin. * @param retrieve The host-provided retrieve callback. diff --git a/ext/persist.lv2/persist.ttl b/ext/persist.lv2/persist.ttl index 1cbbdb7..490e2c1 100644 --- a/ext/persist.lv2/persist.ttl +++ b/ext/persist.lv2/persist.ttl @@ -47,38 +47,97 @@ This extension provides a mechanism for plugins to save and restore state across instances, allowing hosts to save configuration/state/data with a project or fully clone a plugin instance (including internal state). -The motivating idea behind this extension is that a plugin instance's state -is entirely represented by port values and a single key/value dictionary. -This makes state well-defined and easily manageable by hosts. Keys are URIs, -avoiding conflicts and allowing the same dictionary to be used to store plugin -state in any context. Values are typed tagged (by URI mapped integers), -but otherwise are simple binary blobs. - -This extension defines plugin instance state and provides a mechanism -for saving/restoring it, but otherwise makes no restrictions on how a -plugin works with state. For example, other extensions may define dynamic -ways to control plugin state at runtime. The idea is that <em>all</em> -plugin state can be represented with a single (conceptual) dictionary. -This state representation is tried-and-true, universal, and works well with -many existing technologies. Accordingly, plugins/extensions that deal with -instance state in any way SHOULD represent it in a way compatible with this -extension, i.e. URI keys with URI-typed values. Similarly, plugins SHOULD NOT -use any other mechanism to store/restore state; this <strong>will</strong> -cause serious problems, don't do it! Note that you can store values of any -format whatsoever, so if you have an existing state representation to use -(e.g. XML), simply store it as a single value under some key. - -Files may be persisted using this extension in conjunction with the -<a href="http://lv2plug.in/ns/ext/files">LV2 Files</a> extension. - -Instance state as defined by this extension is RDF compatible, allowing for -simple and seamless integration with existing technology (LV2 or otherwise). -An obvious benefit of this is that plugin state can be elegantly described -in Turtle files; the persist:instanceState predicate is provided for this -purpose. RDF compatibility is also convenient since LV2 hosts are likely -to already have mechanisms for working with RDF-style data. Note, however, -that hosts may store state in any way, and are not required to use any -specific technology or file format to support this extension. +Unlike ports, this extension allows plugins to save private state data. +The motivating ideal behind this extension is for the state of a plugin +instance to be entirely described by port values (as with all LV2 plugins) and +a key/value dictionary as defined by this extension. This mechanism is simple, +yet sufficiently powerful to describe the state of very advanced plugins. + +The "state" described by this extension is conceptually a single key/value +dictionary. Keys are URIs, and values are typed-tagged blobs of any type. +The plugin provides a save and restore method for saving and restoring state. +To initiate a save or restore, the host calls these methods, passing a callback +to be used for saving or restoring a single key/value pair. In this way, the +actual mechanism of saving and restoring state is completely abstract from the +plugin's perspective. + +Because the state is a simple dictionary, hosts and plugins can work with state +easily (virtually all programming languages have an appropriate dictionary +type available). Additionally, this format is simple and terse to serialise +in many formats (e.g. any RDF syntax, JSON, XML, key/value databases such as +BDB, etc.). In particular, state can be elegantly described in a plugin's +Turtle description, which is useful for presets (among other things). +Note that these are all simply possibilities enabled by this simple data +model. This extension defines only a few function prototypes and does not +impose any requirement to use a particular syntax, data structure, library, +or other implementation detail. Hosts are free to work with plugin state +in whatever way is most appropriate for that host. + +This extension makes it possible for plugins to save private data, but state is +not necessarily private, e.g. a plugin could have a public interface via ports +for manipulating internal state, which would be saved using this extension. +Plugins are strongly encouraged to represent all state change as modifications +of such key/value variables, to minimize implementation burden and enable +the many benefits of having a universal model for describing plugin state. +The use of URI keys prevents conflict and allows unrelated plugins to +meaningfully describe state changes. Future extensions will describe a +dynamic mechanism for manipulating plugin state, as well as define various +keys likely to be useful to a wide range of plugins. + +In pseudo code, a typical use case in a plugin is: +<pre> +static const char* const KEY_GREETING = "http://example.org/greeting"; + +void my_save(LV2_Handle instance, + LV2_Persist_Store_Function store, + void* callback_data) +{ + MyPlugin* plugin = (MyPlugin*)instance; + const char* greeting = plugin->state->greeting; + + store(callback_data, KEY_GREETING, + greeting, strlen(greeting) + 1, + lv2_uri_map("http://lv2plug.in/ns/ext/atom#String")); +} + +void my_restore(LV2_Handle instance, + LV2_Persist_Retrieve_Function retrieve, + void* callback_data) +{ + MyPlugin* plugin = (MyPlugin*)instance; + + size_t size; + uint32_t type; + const char* greeting = retrieve(callback_data, KEY_GREETING, &size, &type); + + if (greeting) + plugin->state->greeting = greeting; + else + plugin->state->greeting = "Hello"; + +} +</pre> + +Similarly, a typical use case in a host is: +<pre> +void store_callback(void* callback_data, + const char* key, + const void* value, + size_t size, + uint32_t type) +{ + Map* state_map = (Map*)callback_data; + state_map->insert(key, Value(value, size, type)); +} + +Map get_plugin_state(LV2_Handle instance) +{ + LV2_Persist* persist = instance.extension_data("http://lv2plug.in/ns/ext/persist"); + Map state_map; + persist.save(instance, store_callback, &state_map); + return state_map; +} +</pre> """ . persist:InstanceState |