We'd like to lower the risk of loosing the change in case of the program crashing, the computer to malfunction or whatsoever.
A common way is to do regular saves in the background. Therefore the app would start a timer when the song has been changes. When the timer reaches 0, it does the autosaving.
Besides the application would use a lock (e.g. gconf-key or file) when starting and remove it when closing down properly. When the application starts and it find the lock still there, it would check for autosaved versions of the last file and ask the user, if he like to restore it. If the user chooses no, they are removed. If the users chooses yes, the autosave is loaded (and file-name changed to proper file-name). User can verify and save or discard the autosave.
This might have some disadvantages. Its hard to make atomic saving, if we don't block the ui. Blocking the ui would be bad. Also it might disturb the playback. A quick check shows that saving Aenathron.bmx as xml takes 0.08 sec only and as zipped xml it takes 0.1 sec. This will be different for songs with lots of waveforms.
A better way is to write all edit actions to a log file. Whenever the song has been saved successfully, the log is reset as well. If one opens a file and there is a non-empty log, ask the user if the log should be replayed. This is linked to the full undo, redo feature as a undo/redo stack is our log in a serialized form. For a new song this looks like
-o- undo/redo/save pointer = NULL `- new/loaded song
After a bit of work, it might look like this:
action7 -o action6 -o- undo/redo pointer action5 -o action4 -o action3 -o- save pointer action2 -o action1 -o `- new/loaded song
What can we read from this
- we have done one undo (can do one redo)
- if undo_ptr.has_next() we can do undo
- if redo_ptr.has_pref() we can do redo
- if save_ptr && save_ptr.has_next() song is changed (can save)
There are three basic operations:
create - create and add a new object
grep "_new(const BtSong" *.c
modify - change an object
destroy - remove and dispose an object
bt_setup_remove_machine bt_setup_remove_wire bt_machine_remove_pattern
Some object exists implicitely in a song - they are always there and there is just one of them:
Other objects can exist not at all or multiple times:
- machine (in setup)
- wire (in setup)
- pattern (in machine)
- wire-pattern (in wire)
- wave (in wavetable)
- wave-level (in wave)
the log (v1)
// create two objects BtMachine.create(plugin-name=simsyn, id=... BtWire.create(src-machine=..., dst-machine=...) // move the machine somewhere (difficult as xpos,ypos are no object properties) BtMachine.modify(object-id, properties.xpos=xxx, properties.ypos=yyy)
For elements where we can have multiple ones, the first param needs to be the object id.
the log (v2)
// create two objects setup.create(plugin-name=simsyn, id=... setup.create(src-machine=..., dst-machine=...) // move the machine somewhere (difficult as xpos,ypos are no object properties) setup/machine/simsyn.modify(properties.xpos=xxx, properties.ypos=yyy) // add a pattern setup/machine/simsyn.create(id="beep",length=10,voices=0) // set a value in a pattern (again here is no property for it) setup/machine/simsyn/beep.modify(tick=10, group=global, param=freq, value=100.0)
libbuzztrax-core can provide the session-log (as a singleton). Certain functions would write the log-entries. The session-log would provide helpers to e.g. build the object path. The persistence class has static serialization/deserialization functions. The session-log object has the replay method. The replay method can check the object-path, lookup the object and then dispatch to the respective class. Could we implement something like GstChildProxy to provide the object lookup.
Would be cool if we could reuse the persistence interface implementation.
To do a lookup for a given path "setup/machine/simsyn/beep" we'll always start at song. This looks at the first item "setup" and dispatches or errors out (e.g. if path is "oops/peng/..."). Setup in turn looks at the 2nd item "machine", it can resolv machines itself, thus looks at the 3rd item and resolves.
When objects are created we'll assign them the path, so that when logging, we have the path ready. When we build the song, we tell setup, that its path is "setup" and wavetable that its path is "wavetable". When we add a machine to setup, setup tells the machine that the path is "setup/machines/simsyn", that is "<own-path>/machines/<machine-id>".
BtObjectPath // recursively called until it return NULL for unknown/failure or an object GObject *bt_object_path_lookup(GObject *object, gchar *path); // ideally each object gets the path assigned upon object creation bt_object_path_set(GObject *object, gchar *path);
- add timestamps? could be used to do a realtime replay for e.g. demos
- how to identify objects and how to generalize how they can be looked up