Undo: unified undo system w/ linear history
[blender.git] / source / blender / editors / curve / editfont_undo.c
1 /*
2  * ***** BEGIN GPL LICENSE BLOCK *****
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software Foundation,
16  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  *
18  * ***** END GPL LICENSE BLOCK *****
19  */
20
21 /** \file blender/editors/curve/editfont_undo.c
22  *  \ingroup edcurve
23  */
24
25 #include <stdlib.h>
26 #include <string.h>
27 #include <wchar.h>
28
29 #include "MEM_guardedalloc.h"
30
31 #include "BLI_utildefines.h"
32 #include "BLI_array_utils.h"
33
34
35 #include "DNA_curve_types.h"
36 #include "DNA_object_types.h"
37 #include "DNA_scene_types.h"
38
39 #include "BKE_context.h"
40 #include "BKE_font.h"
41 #include "BKE_depsgraph.h"
42 #include "BKE_undo_system.h"
43
44 #include "ED_object.h"
45 #include "ED_curve.h"
46 #include "ED_util.h"
47
48 #include "WM_types.h"
49 #include "WM_api.h"
50
51 #define USE_ARRAY_STORE
52
53 #ifdef USE_ARRAY_STORE
54 // #  define DEBUG_PRINT
55 #  include "BLI_array_store.h"
56 #  include "BLI_array_store_utils.h"
57 #  include "BLI_listbase.h"
58 #  define ARRAY_CHUNK_SIZE 32
59 #endif
60
61 /* -------------------------------------------------------------------- */
62 /** \name Undo Conversion
63  * \{ */
64
65 typedef struct UndoFont {
66         wchar_t *textbuf;
67         struct CharInfo *textbufinfo;
68
69         int len, pos;
70
71 #ifdef USE_ARRAY_STORE
72         struct {
73                 BArrayState *textbuf;
74                 BArrayState *textbufinfo;
75         } store;
76 #endif
77
78         size_t undo_size;
79 } UndoFont;
80
81
82 #ifdef USE_ARRAY_STORE
83
84 /** \name Array Store
85  * \{ */
86
87 static struct {
88         struct BArrayStore_AtSize bs_stride;
89         int users;
90
91         /* We could have the undo API pass in the previous state, for now store a local list */
92         ListBase local_links;
93
94 } uf_arraystore = {{NULL}};
95
96 /**
97  * \param create: When false, only free the arrays.
98  * This is done since when reading from an undo state, they must be temporarily expanded.
99  * then discarded afterwards, having this argument avoids having 2x code paths.
100  */
101 static void uf_arraystore_compact_ex(
102         UndoFont *uf, const UndoFont *uf_ref,
103         bool create)
104 {
105 #define STATE_COMPACT(uf, id, len) \
106         if ((uf)->id) { \
107                 BLI_assert(create == ((uf)->store.id == NULL)); \
108                 if (create) { \
109                         BArrayState *state_reference = uf_ref ? uf_ref->store.id : NULL; \
110                         const size_t stride = sizeof(*(uf)->id); \
111                         BArrayStore *bs = BLI_array_store_at_size_ensure(&uf_arraystore.bs_stride, stride, ARRAY_CHUNK_SIZE); \
112                         (uf)->store.id = BLI_array_store_state_add( \
113                                 bs, (uf)->id, (size_t)(len) * stride, state_reference); \
114                 } \
115                 /* keep uf->len for validation */ \
116                 MEM_freeN((uf)->id); \
117                 (uf)->id = NULL; \
118         } ((void)0)
119
120         STATE_COMPACT(uf, textbuf, uf->len + 1);
121         STATE_COMPACT(uf, textbufinfo, uf->len + 1);
122
123 #undef STATE_COMPACT
124
125         if (create) {
126                 uf_arraystore.users += 1;
127         }
128 }
129
130 /**
131  * Move data from allocated arrays to de-duplicated states and clear arrays.
132  */
133 static void uf_arraystore_compact(UndoFont *um, const UndoFont *uf_ref)
134 {
135         uf_arraystore_compact_ex(um, uf_ref, true);
136 }
137
138 static void uf_arraystore_compact_with_info(UndoFont *um, const UndoFont *uf_ref)
139 {
140 #ifdef DEBUG_PRINT
141         size_t size_expanded_prev, size_compacted_prev;
142         BLI_array_store_at_size_calc_memory_usage(&uf_arraystore.bs_stride, &size_expanded_prev, &size_compacted_prev);
143 #endif
144
145         uf_arraystore_compact(um, uf_ref);
146
147 #ifdef DEBUG_PRINT
148         {
149                 size_t size_expanded, size_compacted;
150                 BLI_array_store_at_size_calc_memory_usage(&uf_arraystore.bs_stride, &size_expanded, &size_compacted);
151
152                 const double percent_total = size_expanded ?
153                         (((double)size_compacted / (double)size_expanded) * 100.0) : -1.0;
154
155                 size_t size_expanded_step = size_expanded - size_expanded_prev;
156                 size_t size_compacted_step = size_compacted - size_compacted_prev;
157                 const double percent_step = size_expanded_step ?
158                         (((double)size_compacted_step / (double)size_expanded_step) * 100.0) : -1.0;
159
160                 printf("overall memory use: %.8f%% of expanded size\n", percent_total);
161                 printf("step memory use:    %.8f%% of expanded size\n", percent_step);
162         }
163 #endif
164 }
165
166 /**
167  * Remove data we only expanded for temporary use.
168  */
169 static void uf_arraystore_expand_clear(UndoFont *um)
170 {
171         uf_arraystore_compact_ex(um, NULL, false);
172 }
173
174 static void uf_arraystore_expand(UndoFont *uf)
175 {
176 #define STATE_EXPAND(uf, id, len) \
177         if ((uf)->store.id) { \
178                 const size_t stride = sizeof(*(uf)->id); \
179                 BArrayState *state = (uf)->store.id; \
180                 size_t state_len; \
181                 (uf)->id = BLI_array_store_state_data_get_alloc(state, &state_len); \
182                 BLI_assert((len) == (state_len / stride)); \
183                 UNUSED_VARS_NDEBUG(stride); \
184         } ((void)0)
185
186         STATE_EXPAND(uf, textbuf, uf->len + 1);
187         STATE_EXPAND(uf, textbufinfo, uf->len + 1);
188
189 #undef STATE_EXPAND
190 }
191
192 static void uf_arraystore_free(UndoFont *uf)
193 {
194 #define STATE_FREE(uf, id) \
195         if ((uf)->store.id) { \
196                 const size_t stride = sizeof(*(uf)->id); \
197                 BArrayStore *bs = BLI_array_store_at_size_get(&uf_arraystore.bs_stride, stride); \
198                 BArrayState *state = (uf)->store.id; \
199                 BLI_array_store_state_remove(bs, state); \
200                 (uf)->store.id = NULL; \
201         } ((void)0)
202
203         STATE_FREE(uf, textbuf);
204         STATE_FREE(uf, textbufinfo);
205
206 #undef STATE_FREE
207
208         uf_arraystore.users -= 1;
209
210         BLI_assert(uf_arraystore.users >= 0);
211
212         if (uf_arraystore.users == 0) {
213 #ifdef DEBUG_PRINT
214                 printf("editfont undo store: freeing all data!\n");
215 #endif
216
217                 BLI_array_store_at_size_clear(&uf_arraystore.bs_stride);
218         }
219 }
220
221 /** \} */
222
223 #endif  /* USE_ARRAY_STORE */
224
225 static void undofont_to_editfont(UndoFont *uf, Curve *cu)
226 {
227         EditFont *ef = cu->editfont;
228
229         size_t final_size;
230
231 #ifdef USE_ARRAY_STORE
232         uf_arraystore_expand(uf);
233 #endif
234
235         final_size = sizeof(wchar_t) * (uf->len + 1);
236         memcpy(ef->textbuf, uf->textbuf, final_size);
237
238         final_size = sizeof(CharInfo) * (uf->len + 1);
239         memcpy(ef->textbufinfo, uf->textbufinfo, final_size);
240
241         ef->pos = uf->pos;
242         ef->len = uf->len;
243
244         ef->selstart = ef->selend = 0;
245
246 #ifdef USE_ARRAY_STORE
247         uf_arraystore_expand_clear(uf);
248 #endif
249 }
250
251 static void *undofont_from_editfont(UndoFont *uf, Curve *cu)
252 {
253         BLI_assert(BLI_array_is_zeroed(uf, 1));
254
255         EditFont *ef = cu->editfont;
256
257         size_t mem_used_prev = MEM_get_memory_in_use();
258
259         size_t final_size;
260
261         final_size = sizeof(wchar_t) * (ef->len + 1);
262         uf->textbuf = MEM_mallocN(final_size, __func__);
263         memcpy(uf->textbuf, ef->textbuf, final_size);
264
265         final_size = sizeof(CharInfo) * (ef->len + 1);
266         uf->textbufinfo = MEM_mallocN(final_size, __func__);
267         memcpy(uf->textbufinfo, ef->textbufinfo, final_size);
268
269         uf->pos = ef->pos;
270         uf->len = ef->len;
271
272 #ifdef USE_ARRAY_STORE
273         {
274                 const UndoFont *uf_ref = uf_arraystore.local_links.last ?
275                                          ((LinkData *)uf_arraystore.local_links.last)->data : NULL;
276
277                 /* add oursrlves */
278                 BLI_addtail(&uf_arraystore.local_links, BLI_genericNodeN(uf));
279
280                 uf_arraystore_compact_with_info(uf, uf_ref);
281         }
282 #endif
283
284         size_t mem_used_curr = MEM_get_memory_in_use();
285
286         uf->undo_size = mem_used_prev < mem_used_curr ? mem_used_curr - mem_used_prev : sizeof(UndoFont);
287
288         return uf;
289 }
290
291 static void undofont_free_data(UndoFont *uf)
292 {
293 #ifdef USE_ARRAY_STORE
294         {
295                 LinkData *link = BLI_findptr(&uf_arraystore.local_links, uf, offsetof(LinkData, data));
296                 BLI_remlink(&uf_arraystore.local_links, link);
297                 MEM_freeN(link);
298         }
299         uf_arraystore_free(uf);
300 #endif
301
302         if (uf->textbuf) {
303                 MEM_freeN(uf->textbuf);
304         }
305         if (uf->textbufinfo) {
306                 MEM_freeN(uf->textbufinfo);
307         }
308 }
309
310 static Object *editfont_object_from_context(bContext *C)
311 {
312         Object *obedit = CTX_data_edit_object(C);
313         if (obedit && obedit->type == OB_FONT) {
314                 Curve *cu = obedit->data;
315                 EditFont *ef = cu->editfont;
316                 if (ef != NULL) {
317                         return obedit;
318                 }
319         }
320         return NULL;
321 }
322
323 /** \} */
324
325 /* -------------------------------------------------------------------- */
326 /** \name Implements ED Undo System
327  * \{ */
328
329 typedef struct FontUndoStep {
330         UndoStep step;
331         /* note: will split out into list for multi-object-editmode. */
332         UndoRefID_Object obedit_ref;
333         UndoFont data;
334 } FontUndoStep;
335
336 static bool font_undosys_poll(bContext *C)
337 {
338         return editfont_object_from_context(C) != NULL;
339 }
340
341 static bool font_undosys_step_encode(struct bContext *C, UndoStep *us_p)
342 {
343         FontUndoStep *us = (FontUndoStep *)us_p;
344         us->obedit_ref.ptr = editfont_object_from_context(C);
345         Curve *cu = us->obedit_ref.ptr->data;
346         undofont_from_editfont(&us->data, cu);
347         us->step.data_size = us->data.undo_size;
348         return true;
349 }
350
351 static void font_undosys_step_decode(struct bContext *C, UndoStep *us_p, int UNUSED(dir))
352 {
353         /* TODO(campbell): undo_system: use low-level API to set mode. */
354         ED_object_mode_set(C, OB_MODE_EDIT);
355         BLI_assert(font_undosys_poll(C));
356
357         FontUndoStep *us = (FontUndoStep *)us_p;
358         Object *obedit = us->obedit_ref.ptr;
359         Curve *cu = obedit->data;
360         undofont_to_editfont(&us->data, cu);
361         DAG_id_tag_update(&obedit->id, OB_RECALC_DATA);
362         WM_event_add_notifier(C, NC_GEOM | ND_DATA, NULL);
363 }
364
365 static void font_undosys_step_free(UndoStep *us_p)
366 {
367         FontUndoStep *us = (FontUndoStep *)us_p;
368         undofont_free_data(&us->data);
369 }
370
371 static void font_undosys_foreach_ID_ref(
372         UndoStep *us_p, UndoTypeForEachIDRefFn foreach_ID_ref_fn, void *user_data)
373 {
374         FontUndoStep *us = (FontUndoStep *)us_p;
375         foreach_ID_ref_fn(user_data, ((UndoRefID *)&us->obedit_ref));
376 }
377
378 /* Export for ED_undo_sys. */
379 void ED_font_undosys_type(UndoType *ut)
380 {
381         ut->name = "Edit Font";
382         ut->poll = font_undosys_poll;
383         ut->step_encode = font_undosys_step_encode;
384         ut->step_decode = font_undosys_step_decode;
385         ut->step_free = font_undosys_step_free;
386
387         ut->step_foreach_ID_ref = font_undosys_foreach_ID_ref;
388
389         ut->mode = BKE_UNDOTYPE_MODE_STORE;
390         ut->use_context = true;
391
392         ut->step_size = sizeof(FontUndoStep);
393 }
394
395 /** \} */