forked from muse-sequencer/muse
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathREADME.developer.undo_system
More file actions
178 lines (137 loc) · 7.25 KB
/
Copy pathREADME.developer.undo_system
File metadata and controls
178 lines (137 loc) · 7.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
V 0.0.1 03/26/2020 By Tim.
A mini review of the Operations and Undo system in MusE.
========================================================
Operations:
------------
During each audio cycle segment, a realtime audio thread
processes audio and midi.
A block of audio and midi events are gathered to be played
in the next segment to various outputs, while a block of
incoming audio and midi events from the last segment are
recorded from various inputs.
Various containers house all the audio and midi events
in a song, from which the realtime process thread gathers
the required events for the next playback segment.
When it is desired to alter the contents of the various
containers which house all audio and midi events, and
to do so from a thread other than the realtime thread,
for example the GUI thread, the actual change must be
synchronized with and take place in the realtime audio
thread, otherwise these concurrent accesses will crash.
In a realtime audio thread it is forbidden to use code
that may block or wait or take a long time, such as
mutexes, memory allocations, or printing functions.
So MusE does not use any mutexes or locks of any kind
in the realtime thread. Instead it uses synchronized
OPERATIONS to make desired changes in the realtime
process callback just before audio and midi are processed.
Example: To change a note in a list of notes in a song
from inside a GUI dialog, an OPERATION is requested,
and the GUI thread will pause while the realtime thread
executes the request. The GUI thread resumes when the
OPERATION is done.
Note that it is possible the realtime audio thread may NOT
be running in realtime, for example if the audio system
is not set up for realtime. MusE is fairly tolerant of
that, but it is possible the audio thread may not finish
before the GUI thread runs again, which may cause a
concurrency crash if some list was being changed.
This is why realtime is important and why it is important
to try to make changes as quickly as possible in the audio
thread in case the thread is not realtime - if the changes
are taking a long time in the audio thread, the GUI thread
might run.
Undo/redo:
----------
It is desirable to have an undo/redo for many OPERATIONS.
And yet, not all OPERATIONS can be or need to be undoable.
So for many OPERATIONS, the request is sent to the UNDO system,
instead of directly to the OPERATIONS system. The UNDO system
remembers the OPERATION so that it can be undone and redone.
Still, some requests are sent directly to the OPERATIONS system,
bypassing the UNDO system.
In addition, the UNDO system has the ability to process some
requests WITHOUT remembering them for undoing, basically
passing them on to the OPERATIONS system.
This is convenient for example when a list of UNDO OPERATIONS
contains some OPERATIONS that should not be undoable. This
eases the caller's composition of lists of requests, using
only the UNDO system instead of working both the UNDO and
OPERATIONS systems.
The UNDO system has a few tricks. It can combine previously
executed commands into one list. It can reduce repeated
similar commands to just one, such as when operating a mouse
to adjust some value - only the last value at mouse-up is stored
instead of storing hundreds of values in hundreds of commands.
Operations vs. Undo Operations
-------------------------------
A distinction should be made between OPERATIONS and UNDO OPERATIONS:
Although the UNDO system takes requests and composes and executes
OPERATIONS, UNDO requests are not the same as OPERATION requests.
The caller does not pass OPERATION requests to the UNDO system,
the caller passes UNDO OPERATIONS. Owing to the different nature
of the two systems, the composition and structure of the requests
is different. For example OPERATIONS system has the ModifyTempoList
OPERATION, but the UNDO system has AddTempo DeleteTempo and ModifyTempo
UNDO OPERATIONS.
Stages:
-------
Both the OPERATIONS and UNDO systems work in STAGES:
* OPERATIONS system:
The OPERATIONS system has two STAGES: A REALTIME STAGE followed by
a NON-REALTIME STAGE.
The idea is that the caller composes an OPERATION beforehand in the
GUI thread, then passes it to the OPERATIONS system where any
realtime-sensitive changes occur in the REALTIME STAGE, followed by
any non-realtime-sensitive code in the NON-REALTIME STAGE such as
cleanups, safe deletion of memory no longer used, etc.
* UNDO system:
The UNDO system has three STAGES: A NON-REALTIME STAGE (1), followed
by a REALTIME STAGE (2) which basically just calls the OPERATIONS
system REALTIME STAGE, followed by a NON-REALTIME STAGE (3) which
basically just calls the OPERATIONS system NON-REALTIME STAGE.
The UNDO system NON-REALTIME STAGE (1) is a convenient place where any
non-realtime-sensitive code can by run BEFORE the REALTIME STAGE.
It offers a place to compose complex preparations for a request
instead of the caller having to do it, trying to make the caller's
job as simple as possible. For example memory allocation.
The OPERATIONS system has no such STAGE. The caller must compose
the OPERATION and handle any allocation etc. although the system's
NON-REALTIME STAGE can be used to cleanup, deallocate etc.
Altering lists vs. swapping them out quickly:
---------------------------------------------
In the OPERATIONS system's realtime code, the idea is to execute
only the bare minimum code required to make the changes. Such as
inserting in a list, or calling erase with an iterator that was
already found in the GUI thread so that erase is 'ready to go'.
Before the OPERATIONS system was added, the UNDO system handled
all of this, but it was ALSO executing some awfully heavy code.
But even though those list insert/erase techniques are as tight
as they can be, they STILL may allocate or de-allocate memory,
which is not realtime safe !
One way to guarantee very fast changes to containers (fixed time)
is to completely swap them with another list, composed beforehand
with the desired changes.
If the container happens to be a pointer-to, the pointer can be
quickly, even atomically, swapped with another pointer-to,
guaranteeing no hiccups or xruns in the processing.
If the container is NOT a pointer-to, unfortunately there is no
pointer to swap, since the container is the object.
However some containers have a swap() method which has very fast
fixed-time complexity, which really helps in this situation.
Note with this technique, the UNDO system does NOT store a copy
of the container for each UNDO OPERATION in the UNDO history.
The UNDO system only stores the necessary CHANGES, saving memory.
When an actual Undo is performed, only then is a new replacement
list composed based on those changes, and then swapped out with
the original.
Several OPERATIONS have now been switched over to this technique.
One caveat and inefficiency is that with a very large container
needing only a simple change, you will be making a copy of it,
albeit only for a brief time until swapped.
Tempo, time signature, key, and other special operations:
---------------------------------------------------------
These three tempo, sig and key areas have very special needs
due to how these containers operate. The code is somewhat complex.
In addition certain other operations have special needs and have
more complex code.