Repository: godotengine/godot
File: core/object/undo_redo.cpp (C++)

Code before:
	actions.write[current_action + 1].do_ops.push_back(do_op);
}

void UndoRedo::add_undo_method(const Callable &p_callable) {
	ERR_FAIL_COND(p_callable.is_null());
	ERR_FAIL_COND(action_level <= 0);
	ERR_FAIL_COND((current_action + 1) >= actions.size());

	// No undo if the merge mode is MERGE_ENDS
	if (!force_keep_in_merge_ends && merge_mode == MERGE_ENDS) {
		return;
	}

	Object *object = p_callable.get_object();
	ERR_FAIL_NULL(object);

	Operation undo_op;
	undo_op.callable = p_callable;
	undo_op.object = p_callable.get_object_id();
	if (Object::cast_to<RefCounted>(object)) {
		undo_op.ref = Ref<RefCounted>(Object::cast_to<RefCounted>(object));
	}
	undo_op.type = Operation::TYPE_METHOD;
	undo_op.force_keep_in_merge_ends = force_keep_in_merge_ends;
	undo_op.name = p_callable.get_method();

	actions.write[current_action + 1].undo_ops.push_back(undo_op);
}

void UndoRedo::add_do_property(Object *p_object, const StringName &p_property, const Variant &p_value) {
	ERR_FAIL_NULL(p_object);
	ERR_FAIL_COND(action_level <= 0);
	ERR_FAIL_COND((current_action + 1) >= actions.size());
	Operation do_op;
	do_op.object = p_object->get_instance_id();
	if (Object::cast_to<RefCounted>(p_object)) {
		do_op.ref = Ref<RefCounted>(Object::cast_to<RefCounted>(p_object));
	}

	do_op.type = Operation::TYPE_PROPERTY;
	do_op.name = p_property;
	do_op.value = p_value;
	actions.write[current_action + 1].do_ops.push_back(do_op);
}

void UndoRedo::add_undo_property(Object *p_object, const StringName &p_property, const Variant &p_value) {
	ERR_FAIL_NULL(p_object);
	ERR_FAIL_COND(action_level <= 0);
	ERR_FAIL_COND((current_action + 1) >= actions.size());

	// No undo if the merge mode is MERGE_ENDS

Review comment (performance):
I've been investigating the performance of `cast_to()` in the case of null objects. My suggestion would be to avoid doing the null check explicitly at the call site and let `cast_to()` do it if desired (it will be inlined). I'm making a PR precisely adding the check so it will be redundant here.

Code after:
	if (do_op.name == StringName()) {
		// There's no `get_method()` for custom callables, so use `operator String()` instead.
		do_op.name = static_cast<String>(p_callable);
	}

	actions.write[current_action + 1].do_ops.push_back(do_op);
}

void UndoRedo::add_undo_method(const Callable &p_callable) {
	ERR_FAIL_COND(p_callable.is_null());
	ERR_FAIL_COND(action_level <= 0);
	ERR_FAIL_COND((current_action + 1) >= actions.size());

	// No undo if the merge mode is MERGE_ENDS
	if (!force_keep_in_merge_ends && merge_mode == MERGE_ENDS) {
		return;
	}

	ObjectID object_id = p_callable.get_object_id();
	Object *object = ObjectDB::get_instance(object_id);
	ERR_FAIL_COND(object_id.is_valid() && object == nullptr);

	Operation undo_op;
	undo_op.callable = p_callable;
	undo_op.object = object_id;
	if (Object::cast_to<RefCounted>(object)) {
		undo_op.ref = Ref<RefCounted>(Object::cast_to<RefCounted>(object));
	}
	undo_op.type = Operation::TYPE_METHOD;
	undo_op.force_keep_in_merge_ends = force_keep_in_merge_ends;
	undo_op.name = p_callable.get_method();
	if (undo_op.name == StringName()) {
		// There's no `get_method()` for custom callables, so use `operator String()` instead.
		undo_op.name = static_cast<String>(p_callable);
	}

	actions.write[current_action + 1].undo_ops.push_back(undo_op);
}

void UndoRedo::add_do_property(Object *p_object, const StringName &p_property, const Variant &p_value) {
	ERR_FAIL_NULL(p_object);
	ERR_FAIL_COND(action_level <= 0);
	ERR_FAIL_COND((current_action + 1) >= actions.size());
	Operation do_op;
	do_op.object = p_object->get_instance_id();
	if (Object::cast_to<RefCounted>(p_object)) {
		do_op.ref = Ref<RefCounted>(Object::cast_to<RefCounted>(p_object));
	}

	do_op.type = Operation::TYPE_PROPERTY;
	do_op.name = p_property;