1 /*
2 Drawpile - a collaborative drawing program.
3
4 Copyright (C) 2015-2017 Calle Laakkonen
5
6 Drawpile is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 Drawpile is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with Drawpile. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "retcon.h"
21
22 using protocol::MessagePtr;
23
24 namespace canvas {
25
isConcurrentWith(const AffectedArea & other) const26 bool AffectedArea::isConcurrentWith(const AffectedArea &other) const
27 {
28 if(m_domain == EVERYTHING || other.m_domain == EVERYTHING)
29 return false;
30
31 if(m_domain == USERATTRS || m_domain != other.m_domain || m_layer != other.m_layer)
32 return true;
33
34 // for pixel changes, the effect bounding rectangles must not intersect
35 if(m_domain == PIXELS && !m_bounds.intersects(other.m_bounds))
36 return true;
37
38 return false;
39 }
40
setOffset(int offset)41 void LocalFork::setOffset(int offset)
42 {
43 m_offset = offset;
44 }
45
addLocalMessage(MessagePtr msg,const AffectedArea & area)46 void LocalFork::addLocalMessage(MessagePtr msg, const AffectedArea &area)
47 {
48 m_messages.append(msg);
49 m_areas.append(area);
50 }
51
clear()52 void LocalFork::clear()
53 {
54 m_fallenBehind = 0;
55 m_messages.clear();
56 m_areas.clear();
57 }
58
handleReceivedMessage(MessagePtr msg,const AffectedArea & area)59 LocalFork::MessageAction LocalFork::handleReceivedMessage(MessagePtr msg, const AffectedArea &area)
60 {
61 // No local fork: nothing to do. It is possible that we get a message from ourselves
62 // that is not in the local fork, but this is not an error. It could happen when
63 // playing back recordings, for example.
64 if(m_messages.isEmpty())
65 return CONCURRENT;
66
67 // Check if this is our own message that has finished its roundtrip
68 if(msg->contextId() == m_messages.first()->contextId()) {
69 if(msg.equals(m_messages.first())) {
70 m_messages.removeFirst();
71 m_areas.removeFirst();
72 if(m_messages.isEmpty())
73 m_fallenBehind = 0;
74 return ALREADYDONE;
75
76 } else {
77 // Unusual, but not (necessarily) an error. This can happen when the layer is locked
78 // while drawing or when an operator performs some function on behalf the user.
79 qWarning("Local fork out of sync, discarding %d messages!", m_messages.size());
80 qDebug(" Got: %s", qPrintable(msg->toString()));
81 qDebug("Expected: %s", qPrintable(m_messages.first()->toString()));
82 clear();
83 return ROLLBACK;
84 }
85 }
86
87 // OK, so this is another user's message. Check if it is concurrent
88
89 // But first, check if the local fork has fallen too much behind
90 if(m_maxFallBehind>0) {
91 if(++m_fallenBehind >= m_maxFallBehind) {
92 qWarning("Fell behind %d >= %d", m_fallenBehind, m_maxFallBehind);
93 clear();
94 return ROLLBACK;
95 }
96 }
97
98 for(const AffectedArea &a : m_areas) {
99 if(!area.isConcurrentWith(a)) {
100 return ROLLBACK;
101 }
102 }
103
104 return CONCURRENT;
105 }
106
107 }
108