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 #ifndef DP_RETCON_H
21 #define DP_RETCON_H
22 
23 #include "../../libshared/net/message.h"
24 
25 #include <QRect>
26 #include <QList>
27 
28 namespace canvas {
29 
30 /**
31  * @brief Bounds of an operations area of effect
32  *
33  * This is used to check whether received operations are causally dependent or concurrent with
34  * the ones in the local fork.
35  */
36 class AffectedArea
37 {
38 public:
39 	enum Domain {
40 		USERATTRS, // user attributes: always concurrent with local user's operations
41 		LAYERATTRS, // layer attributes
42 		ANNOTATION, // annotations: layer ID is used as the annotation ID
43 		PIXELS, // layer content: bounding rectangles are checked
44 		EVERYTHING // fallback
45 	};
46 
AffectedArea()47 	AffectedArea() : m_domain(EVERYTHING), m_layer(0), m_bounds(QRect()) { }
48 
49 	AffectedArea(Domain domain, int layer, const QRect &bounds=QRect())
m_domain(domain)50 		: m_domain(domain), m_layer(layer), m_bounds(bounds) { }
51 
52 	bool isConcurrentWith(const AffectedArea &other) const;
53 
54 private:
55 	Domain m_domain;
56 	int m_layer;
57 	QRect m_bounds;
58 };
59 
60 }
61 
62 Q_DECLARE_TYPEINFO(canvas::AffectedArea, Q_MOVABLE_TYPE);
63 
64 namespace canvas {
65 
66 /**
67  * @brief Local fork of the session history
68  */
69 class LocalFork
70 {
71 public:
72 	enum MessageAction {
73 		CONCURRENT,  // message was concurrent with local fork: OK to apply
74 		ALREADYDONE, // message was found at the tip of the local fork
75 		ROLLBACK     // message was not concurrent with the local fork: rollback is needed
76 	};
77 
LocalFork()78 	LocalFork() : m_offset(0), m_maxFallBehind(0), m_fallenBehind(0) { }
79 
80 	/**
81 	 * @brief Set the maximum number of messages the local fork is allowed to fall behind the mainline history
82 	 *
83 	 * If set, the a ROLLBACK is forced once the local fork is behind the mainline branch tip by the given
84 	 * number of messages.
85 	 *
86 	 * This can be used prevent stale messages from sticking around in the local fork forever,
87 	 * blocking the creation of new state savepoints.
88 	 * @param fb
89 	 */
setFallbehind(int fb)90 	void setFallbehind(int fb) { m_maxFallBehind = fb; }
91 
92 	/**
93 	 * @brief Add a message to the local fork
94 	 * @param msg
95 	 * @param area
96 	 */
97 	void addLocalMessage(protocol::MessagePtr msg, const AffectedArea &area);
98 
99 	/**
100 	 * @brief Handle a message received from the server.
101 	 *
102 	 * Possible responses:
103 	 * - our own message: the message is removed from the local fork
104 	 * - our own message (out of order): local fork is cleared: need to roll back
105 	 * - other user's message (concurrent): no need to do anything special
106 	 * - other user's message (dependent): need to roll back
107 	 *
108 	 * When an inconsistency is detected, this function returns ROLLBACK. In that case,
109 	 * the canvas should be rolled back to some savepoint before the fork, the fork moved
110 	 * to the end of the session history, and the history then replayed.
111 	 *
112 	 * @param msg
113 	 * @param area
114 	 * @return message action
115 	 */
116 	MessageAction handleReceivedMessage(protocol::MessagePtr msg, const AffectedArea &area);
117 
118 	/**
119 	 * @brief Change local fork offset
120 	 *
121 	 * This is used when doing a canvas rollback: the local fork is moved to the end of the history
122 	 *
123 	 * @param offset new offset
124 	 */
125 	void setOffset(int offset);
126 
127 	/**
128 	 * @brief Get the fork offset
129 	 * @return fork's position in the session history
130 	 */
offset()131 	int offset() const { return m_offset; }
132 
133 	/**
134 	 * @brief Are there any messages in the local fork?
135 	 * @return true if local for is empty
136 	 */
isEmpty()137 	bool isEmpty() const { return m_messages.isEmpty(); }
138 
139 	/**
140 	 * @brief Get the messages in the local fork
141 	 * @return
142 	 */
messages()143 	protocol::MessageList messages() const { return m_messages; }
144 
145 	/**
146 	 * @brief Empty the local fork
147 	 */
148 	void clear();
149 
150 private:
151 	protocol::MessageList m_messages;
152 	QList<AffectedArea> m_areas;
153 	int m_offset;
154 	int m_maxFallBehind;
155 	int m_fallenBehind;
156 };
157 
158 }
159 
160 #endif // LOCALFORK_H
161