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