1/**************************************************************************/
2/* undo_redo.cpp */
3/**************************************************************************/
4/* This file is part of: */
5/* GODOT ENGINE */
6/* https://godotengine.org */
7/**************************************************************************/
8/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10/* */
11/* Permission is hereby granted, free of charge, to any person obtaining */
12/* a copy of this software and associated documentation files (the */
13/* "Software"), to deal in the Software without restriction, including */
14/* without limitation the rights to use, copy, modify, merge, publish, */
15/* distribute, sublicense, and/or sell copies of the Software, and to */
16/* permit persons to whom the Software is furnished to do so, subject to */
17/* the following conditions: */
18/* */
19/* The above copyright notice and this permission notice shall be */
20/* included in all copies or substantial portions of the Software. */
21/* */
22/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29/**************************************************************************/
30
31#include "undo_redo.h"
32
33#include "core/io/resource.h"
34#include "core/os/os.h"
35#include "core/templates/local_vector.h"
36
37void UndoRedo::Operation::delete_reference() {
38 if (type != Operation::TYPE_REFERENCE) {
39 return;
40 }
41 if (ref.is_valid()) {
42 ref.unref();
43 } else {
44 Object *obj = ObjectDB::get_instance(object);
45 if (obj) {
46 memdelete(obj);
47 }
48 }
49}
50
51void UndoRedo::_discard_redo() {
52 if (current_action == actions.size() - 1) {
53 return;
54 }
55
56 for (int i = current_action + 1; i < actions.size(); i++) {
57 for (Operation &E : actions.write[i].do_ops) {
58 E.delete_reference();
59 }
60 //ERASE do data
61 }
62
63 actions.resize(current_action + 1);
64}
65
66bool UndoRedo::_redo(bool p_execute) {
67 ERR_FAIL_COND_V(action_level > 0, false);
68
69 if ((current_action + 1) >= actions.size()) {
70 return false; //nothing to redo
71 }
72
73 current_action++;
74 if (p_execute) {
75 _process_operation_list(actions.write[current_action].do_ops.front());
76 }
77 version++;
78 emit_signal(SNAME("version_changed"));
79
80 return true;
81}
82
83void UndoRedo::create_action(const String &p_name, MergeMode p_mode, bool p_backward_undo_ops) {
84 uint64_t ticks = OS::get_singleton()->get_ticks_msec();
85
86 if (action_level == 0) {
87 _discard_redo();
88
89 // Check if the merge operation is valid
90 if (p_mode != MERGE_DISABLE && actions.size() && actions[actions.size() - 1].name == p_name && actions[actions.size() - 1].backward_undo_ops == p_backward_undo_ops && actions[actions.size() - 1].last_tick + 800 > ticks) {
91 current_action = actions.size() - 2;
92
93 if (p_mode == MERGE_ENDS) {
94 // Clear all do ops from last action if they are not forced kept
95 LocalVector<List<Operation>::Element *> to_remove;
96 for (List<Operation>::Element *E = actions.write[current_action + 1].do_ops.front(); E; E = E->next()) {
97 if (!E->get().force_keep_in_merge_ends) {
98 to_remove.push_back(E);
99 }
100 }
101
102 for (List<Operation>::Element *E : to_remove) {
103 // Delete all object references
104 E->get().delete_reference();
105 E->erase();
106 }
107 }
108
109 actions.write[actions.size() - 1].last_tick = ticks;
110
111 // Revert reverse from previous commit.
112 if (actions[actions.size() - 1].backward_undo_ops) {
113 actions.write[actions.size() - 1].undo_ops.reverse();
114 }
115
116 merge_mode = p_mode;
117 merging = true;
118 } else {
119 Action new_action;
120 new_action.name = p_name;
121 new_action.last_tick = ticks;
122 new_action.backward_undo_ops = p_backward_undo_ops;
123 actions.push_back(new_action);
124
125 merge_mode = MERGE_DISABLE;
126 }
127 }
128
129 action_level++;
130
131 force_keep_in_merge_ends = false;
132}
133
134void UndoRedo::add_do_method(const Callable &p_callable) {
135 ERR_FAIL_COND(p_callable.is_null());
136 ERR_FAIL_COND(action_level <= 0);
137 ERR_FAIL_COND((current_action + 1) >= actions.size());
138
139 Object *object = p_callable.get_object();
140 ERR_FAIL_NULL(object);
141
142 Operation do_op;
143 do_op.callable = p_callable;
144 do_op.object = p_callable.get_object_id();
145 if (Object::cast_to<RefCounted>(object)) {
146 do_op.ref = Ref<RefCounted>(Object::cast_to<RefCounted>(object));
147 }
148 do_op.type = Operation::TYPE_METHOD;
149 do_op.name = p_callable.get_method();
150
151 actions.write[current_action + 1].do_ops.push_back(do_op);
152}
153
154void UndoRedo::add_undo_method(const Callable &p_callable) {
155 ERR_FAIL_COND(p_callable.is_null());
156 ERR_FAIL_COND(action_level <= 0);
157 ERR_FAIL_COND((current_action + 1) >= actions.size());
158
159 // No undo if the merge mode is MERGE_ENDS
160 if (!force_keep_in_merge_ends && merge_mode == MERGE_ENDS) {
161 return;
162 }
163
164 Object *object = p_callable.get_object();
165 ERR_FAIL_NULL(object);
166
167 Operation undo_op;
168 undo_op.callable = p_callable;
169 undo_op.object = p_callable.get_object_id();
170 if (Object::cast_to<RefCounted>(object)) {
171 undo_op.ref = Ref<RefCounted>(Object::cast_to<RefCounted>(object));
172 }
173 undo_op.type = Operation::TYPE_METHOD;
174 undo_op.force_keep_in_merge_ends = force_keep_in_merge_ends;
175 undo_op.name = p_callable.get_method();
176
177 actions.write[current_action + 1].undo_ops.push_back(undo_op);
178}
179
180void UndoRedo::add_do_property(Object *p_object, const StringName &p_property, const Variant &p_value) {
181 ERR_FAIL_NULL(p_object);
182 ERR_FAIL_COND(action_level <= 0);
183 ERR_FAIL_COND((current_action + 1) >= actions.size());
184 Operation do_op;
185 do_op.object = p_object->get_instance_id();
186 if (Object::cast_to<RefCounted>(p_object)) {
187 do_op.ref = Ref<RefCounted>(Object::cast_to<RefCounted>(p_object));
188 }
189
190 do_op.type = Operation::TYPE_PROPERTY;
191 do_op.name = p_property;
192 do_op.value = p_value;
193 actions.write[current_action + 1].do_ops.push_back(do_op);
194}
195
196void UndoRedo::add_undo_property(Object *p_object, const StringName &p_property, const Variant &p_value) {
197 ERR_FAIL_NULL(p_object);
198 ERR_FAIL_COND(action_level <= 0);
199 ERR_FAIL_COND((current_action + 1) >= actions.size());
200
201 // No undo if the merge mode is MERGE_ENDS
202 if (!force_keep_in_merge_ends && merge_mode == MERGE_ENDS) {
203 return;
204 }
205
206 Operation undo_op;
207 undo_op.object = p_object->get_instance_id();
208 if (Object::cast_to<RefCounted>(p_object)) {
209 undo_op.ref = Ref<RefCounted>(Object::cast_to<RefCounted>(p_object));
210 }
211
212 undo_op.type = Operation::TYPE_PROPERTY;
213 undo_op.force_keep_in_merge_ends = force_keep_in_merge_ends;
214 undo_op.name = p_property;
215 undo_op.value = p_value;
216 actions.write[current_action + 1].undo_ops.push_back(undo_op);
217}
218
219void UndoRedo::add_do_reference(Object *p_object) {
220 ERR_FAIL_NULL(p_object);
221 ERR_FAIL_COND(action_level <= 0);
222 ERR_FAIL_COND((current_action + 1) >= actions.size());
223 Operation do_op;
224 do_op.object = p_object->get_instance_id();
225 if (Object::cast_to<RefCounted>(p_object)) {
226 do_op.ref = Ref<RefCounted>(Object::cast_to<RefCounted>(p_object));
227 }
228
229 do_op.type = Operation::TYPE_REFERENCE;
230 actions.write[current_action + 1].do_ops.push_back(do_op);
231}
232
233void UndoRedo::add_undo_reference(Object *p_object) {
234 ERR_FAIL_NULL(p_object);
235 ERR_FAIL_COND(action_level <= 0);
236 ERR_FAIL_COND((current_action + 1) >= actions.size());
237
238 // No undo if the merge mode is MERGE_ENDS
239 if (!force_keep_in_merge_ends && merge_mode == MERGE_ENDS) {
240 return;
241 }
242
243 Operation undo_op;
244 undo_op.object = p_object->get_instance_id();
245 if (Object::cast_to<RefCounted>(p_object)) {
246 undo_op.ref = Ref<RefCounted>(Object::cast_to<RefCounted>(p_object));
247 }
248
249 undo_op.type = Operation::TYPE_REFERENCE;
250 undo_op.force_keep_in_merge_ends = force_keep_in_merge_ends;
251 actions.write[current_action + 1].undo_ops.push_back(undo_op);
252}
253
254void UndoRedo::start_force_keep_in_merge_ends() {
255 ERR_FAIL_COND(action_level <= 0);
256 ERR_FAIL_COND((current_action + 1) >= actions.size());
257
258 force_keep_in_merge_ends = true;
259}
260
261void UndoRedo::end_force_keep_in_merge_ends() {
262 ERR_FAIL_COND(action_level <= 0);
263 ERR_FAIL_COND((current_action + 1) >= actions.size());
264
265 force_keep_in_merge_ends = false;
266}
267
268void UndoRedo::_pop_history_tail() {
269 _discard_redo();
270
271 if (!actions.size()) {
272 return;
273 }
274
275 for (Operation &E : actions.write[0].undo_ops) {
276 E.delete_reference();
277 }
278
279 actions.remove_at(0);
280 if (current_action >= 0) {
281 current_action--;
282 }
283}
284
285bool UndoRedo::is_committing_action() const {
286 return committing > 0;
287}
288
289void UndoRedo::commit_action(bool p_execute) {
290 ERR_FAIL_COND(action_level <= 0);
291 action_level--;
292 if (action_level > 0) {
293 return; //still nested
294 }
295
296 if (merging) {
297 version--;
298 merging = false;
299 }
300
301 if (actions[actions.size() - 1].backward_undo_ops) {
302 actions.write[actions.size() - 1].undo_ops.reverse();
303 }
304
305 committing++;
306 _redo(p_execute); // perform action
307 committing--;
308
309 if (callback && actions.size() > 0) {
310 callback(callback_ud, actions[actions.size() - 1].name);
311 }
312}
313
314void UndoRedo::_process_operation_list(List<Operation>::Element *E) {
315 const int PREALLOCATE_ARGS_COUNT = 16;
316
317 LocalVector<const Variant *> args;
318 args.reserve(PREALLOCATE_ARGS_COUNT);
319
320 for (; E; E = E->next()) {
321 Operation &op = E->get();
322
323 Object *obj = ObjectDB::get_instance(op.object);
324 if (!obj) { //may have been deleted and this is fine
325 continue;
326 }
327
328 switch (op.type) {
329 case Operation::TYPE_METHOD: {
330 Callable::CallError ce;
331 Variant ret;
332 op.callable.callp(nullptr, 0, ret, ce);
333 if (ce.error != Callable::CallError::CALL_OK) {
334 ERR_PRINT("Error calling UndoRedo method operation '" + String(op.name) + "': " + Variant::get_call_error_text(obj, op.name, nullptr, 0, ce));
335 }
336#ifdef TOOLS_ENABLED
337 Resource *res = Object::cast_to<Resource>(obj);
338 if (res) {
339 res->set_edited(true);
340 }
341#endif
342
343 if (method_callback) {
344 Vector<Variant> binds;
345 if (op.callable.is_custom()) {
346 CallableCustomBind *ccb = dynamic_cast<CallableCustomBind *>(op.callable.get_custom());
347 if (ccb) {
348 binds = ccb->get_binds();
349 }
350 }
351
352 if (binds.is_empty()) {
353 method_callback(method_callback_ud, obj, op.name, nullptr, 0);
354 } else {
355 args.clear();
356
357 for (int i = 0; i < binds.size(); i++) {
358 args.push_back(&binds[i]);
359 }
360
361 method_callback(method_callback_ud, obj, op.name, args.ptr(), binds.size());
362 }
363 }
364 } break;
365 case Operation::TYPE_PROPERTY: {
366 obj->set(op.name, op.value);
367#ifdef TOOLS_ENABLED
368 Resource *res = Object::cast_to<Resource>(obj);
369 if (res) {
370 res->set_edited(true);
371 }
372#endif
373 if (property_callback) {
374 property_callback(prop_callback_ud, obj, op.name, op.value);
375 }
376 } break;
377 case Operation::TYPE_REFERENCE: {
378 //do nothing
379 } break;
380 }
381 }
382}
383
384bool UndoRedo::redo() {
385 return _redo(true);
386}
387
388bool UndoRedo::undo() {
389 ERR_FAIL_COND_V(action_level > 0, false);
390 if (current_action < 0) {
391 return false; //nothing to redo
392 }
393 _process_operation_list(actions.write[current_action].undo_ops.front());
394 current_action--;
395 version--;
396 emit_signal(SNAME("version_changed"));
397
398 return true;
399}
400
401int UndoRedo::get_history_count() {
402 ERR_FAIL_COND_V(action_level > 0, -1);
403
404 return actions.size();
405}
406
407int UndoRedo::get_current_action() {
408 ERR_FAIL_COND_V(action_level > 0, -1);
409
410 return current_action;
411}
412
413String UndoRedo::get_action_name(int p_id) {
414 ERR_FAIL_INDEX_V(p_id, actions.size(), "");
415
416 return actions[p_id].name;
417}
418
419void UndoRedo::clear_history(bool p_increase_version) {
420 ERR_FAIL_COND(action_level > 0);
421 _discard_redo();
422
423 while (actions.size()) {
424 _pop_history_tail();
425 }
426
427 if (p_increase_version) {
428 version++;
429 emit_signal(SNAME("version_changed"));
430 }
431}
432
433String UndoRedo::get_current_action_name() const {
434 ERR_FAIL_COND_V(action_level > 0, "");
435 if (current_action < 0) {
436 return "";
437 }
438 return actions[current_action].name;
439}
440
441int UndoRedo::get_action_level() const {
442 return action_level;
443}
444
445bool UndoRedo::has_undo() const {
446 return current_action >= 0;
447}
448
449bool UndoRedo::has_redo() const {
450 return (current_action + 1) < actions.size();
451}
452
453uint64_t UndoRedo::get_version() const {
454 return version;
455}
456
457void UndoRedo::set_commit_notify_callback(CommitNotifyCallback p_callback, void *p_ud) {
458 callback = p_callback;
459 callback_ud = p_ud;
460}
461
462void UndoRedo::set_method_notify_callback(MethodNotifyCallback p_method_callback, void *p_ud) {
463 method_callback = p_method_callback;
464 method_callback_ud = p_ud;
465}
466
467void UndoRedo::set_property_notify_callback(PropertyNotifyCallback p_property_callback, void *p_ud) {
468 property_callback = p_property_callback;
469 prop_callback_ud = p_ud;
470}
471
472UndoRedo::~UndoRedo() {
473 clear_history();
474}
475
476void UndoRedo::_bind_methods() {
477 ClassDB::bind_method(D_METHOD("create_action", "name", "merge_mode", "backward_undo_ops"), &UndoRedo::create_action, DEFVAL(MERGE_DISABLE), DEFVAL(false));
478 ClassDB::bind_method(D_METHOD("commit_action", "execute"), &UndoRedo::commit_action, DEFVAL(true));
479 ClassDB::bind_method(D_METHOD("is_committing_action"), &UndoRedo::is_committing_action);
480
481 ClassDB::bind_method(D_METHOD("add_do_method", "callable"), &UndoRedo::add_do_method);
482 ClassDB::bind_method(D_METHOD("add_undo_method", "callable"), &UndoRedo::add_undo_method);
483 ClassDB::bind_method(D_METHOD("add_do_property", "object", "property", "value"), &UndoRedo::add_do_property);
484 ClassDB::bind_method(D_METHOD("add_undo_property", "object", "property", "value"), &UndoRedo::add_undo_property);
485 ClassDB::bind_method(D_METHOD("add_do_reference", "object"), &UndoRedo::add_do_reference);
486 ClassDB::bind_method(D_METHOD("add_undo_reference", "object"), &UndoRedo::add_undo_reference);
487
488 ClassDB::bind_method(D_METHOD("start_force_keep_in_merge_ends"), &UndoRedo::start_force_keep_in_merge_ends);
489 ClassDB::bind_method(D_METHOD("end_force_keep_in_merge_ends"), &UndoRedo::end_force_keep_in_merge_ends);
490
491 ClassDB::bind_method(D_METHOD("get_history_count"), &UndoRedo::get_history_count);
492 ClassDB::bind_method(D_METHOD("get_current_action"), &UndoRedo::get_current_action);
493 ClassDB::bind_method(D_METHOD("get_action_name", "id"), &UndoRedo::get_action_name);
494 ClassDB::bind_method(D_METHOD("clear_history", "increase_version"), &UndoRedo::clear_history, DEFVAL(true));
495
496 ClassDB::bind_method(D_METHOD("get_current_action_name"), &UndoRedo::get_current_action_name);
497
498 ClassDB::bind_method(D_METHOD("has_undo"), &UndoRedo::has_undo);
499 ClassDB::bind_method(D_METHOD("has_redo"), &UndoRedo::has_redo);
500 ClassDB::bind_method(D_METHOD("get_version"), &UndoRedo::get_version);
501 ClassDB::bind_method(D_METHOD("redo"), &UndoRedo::redo);
502 ClassDB::bind_method(D_METHOD("undo"), &UndoRedo::undo);
503
504 ADD_SIGNAL(MethodInfo("version_changed"));
505
506 BIND_ENUM_CONSTANT(MERGE_DISABLE);
507 BIND_ENUM_CONSTANT(MERGE_ENDS);
508 BIND_ENUM_CONSTANT(MERGE_ALL);
509}
510