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