Fix insertions & test cases, Add debug information
This commit is contained in:
parent
30d454a9d2
commit
e1fc9cf4a7
@ -5,6 +5,7 @@ import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import me.mrletsplay.shareclientcore.connection.message.Message;
|
||||
import me.mrletsplay.shareclientcore.debug.DebugValues;
|
||||
|
||||
public class DummyConnection implements RemoteConnection {
|
||||
|
||||
@ -67,4 +68,9 @@ public class DummyConnection implements RemoteConnection {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public DebugValues getDebugValues() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,12 +1,19 @@
|
||||
package me.mrletsplay.shareclientcore.connection;
|
||||
|
||||
import me.mrletsplay.shareclientcore.connection.message.Message;
|
||||
import me.mrletsplay.shareclientcore.debug.DebugValues;
|
||||
|
||||
/**
|
||||
* Represents a connection to a remote user or server
|
||||
*/
|
||||
public interface RemoteConnection {
|
||||
|
||||
public static final String
|
||||
DEBUG_MESSAGES_SENT = "messagesSent",
|
||||
DEBUG_MESSAGES_RECEIVED = "messagesReceived",
|
||||
DEBUG_CHANGES_SENT = "changesSent",
|
||||
DEBUG_CHANGES_RECEIVED = "changesReceived";
|
||||
|
||||
public static final int PROTOCOL_VERSION = 1;
|
||||
|
||||
public void connect(String sessionID) throws ConnectionException;
|
||||
@ -23,4 +30,6 @@ public interface RemoteConnection {
|
||||
|
||||
public void setDisconnectListener(DisconnectListener listener);
|
||||
|
||||
public DebugValues getDebugValues();
|
||||
|
||||
}
|
||||
|
@ -14,9 +14,11 @@ import org.java_websocket.client.WebSocketClient;
|
||||
import org.java_websocket.framing.CloseFrame;
|
||||
import org.java_websocket.handshake.ServerHandshake;
|
||||
|
||||
import me.mrletsplay.shareclientcore.connection.message.ChangeMessage;
|
||||
import me.mrletsplay.shareclientcore.connection.message.ClientHelloMessage;
|
||||
import me.mrletsplay.shareclientcore.connection.message.Message;
|
||||
import me.mrletsplay.shareclientcore.connection.message.ServerHelloMessage;
|
||||
import me.mrletsplay.shareclientcore.debug.DebugValues;
|
||||
|
||||
public class WebSocketConnection implements RemoteConnection {
|
||||
|
||||
@ -29,11 +31,18 @@ public class WebSocketConnection implements RemoteConnection {
|
||||
private Object wait = new Object();
|
||||
private boolean helloReceived;
|
||||
private ConnectionException connectException;
|
||||
private DebugValues debugValues;
|
||||
|
||||
public WebSocketConnection(URI uri, String username, Map<String, String> httpHeaders) {
|
||||
this.client = new WSClient(uri, httpHeaders);
|
||||
this.username = username;
|
||||
this.listeners = new HashSet<>();
|
||||
this.debugValues = new DebugValues(
|
||||
DEBUG_MESSAGES_SENT,
|
||||
DEBUG_MESSAGES_RECEIVED,
|
||||
DEBUG_CHANGES_SENT,
|
||||
DEBUG_CHANGES_RECEIVED
|
||||
);
|
||||
}
|
||||
|
||||
public WebSocketConnection(URI uri, String username) {
|
||||
@ -68,6 +77,9 @@ public class WebSocketConnection implements RemoteConnection {
|
||||
|
||||
@Override
|
||||
public void send(Message message) throws ConnectionException {
|
||||
debugValues.increment(DEBUG_MESSAGES_SENT);
|
||||
if(message instanceof ChangeMessage) debugValues.increment(DEBUG_CHANGES_SENT);
|
||||
|
||||
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
|
||||
DataOutputStream dOut = new DataOutputStream(bOut);
|
||||
|
||||
@ -96,6 +108,11 @@ public class WebSocketConnection implements RemoteConnection {
|
||||
this.disconnectListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DebugValues getDebugValues() {
|
||||
return debugValues;
|
||||
}
|
||||
|
||||
private class WSClient extends WebSocketClient {
|
||||
|
||||
public WSClient(URI serverUri) {
|
||||
@ -126,6 +143,9 @@ public class WebSocketConnection implements RemoteConnection {
|
||||
return;
|
||||
}
|
||||
|
||||
debugValues.increment(DEBUG_MESSAGES_RECEIVED);
|
||||
if(m instanceof ChangeMessage) debugValues.increment(DEBUG_CHANGES_RECEIVED);
|
||||
|
||||
if(m instanceof ServerHelloMessage hello) {
|
||||
helloReceived = true;
|
||||
siteID = hello.siteID();
|
||||
|
@ -35,7 +35,6 @@ public record FullSyncMessage(int siteID, String documentPath, List<Char> conten
|
||||
List<Char> content = new ArrayList<>(contentSize);
|
||||
for(int i = 0; i < contentSize; i++) {
|
||||
content.add(Char.deserialize(in));
|
||||
System.out.println(content.get(content.size() - 1));
|
||||
}
|
||||
|
||||
return new FullSyncMessage(siteID, documentPath, content);
|
||||
|
@ -0,0 +1,66 @@
|
||||
package me.mrletsplay.shareclientcore.debug;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class DebugValues {
|
||||
|
||||
private Set<String> keys;
|
||||
private Map<String, Integer> values;
|
||||
|
||||
public DebugValues(Collection<String> keys) {
|
||||
this.keys = Collections.unmodifiableSet(new LinkedHashSet<>(keys));
|
||||
this.values = new HashMap<>(this.keys.size());
|
||||
}
|
||||
|
||||
public DebugValues(String... keys) {
|
||||
this(Arrays.asList(keys));
|
||||
}
|
||||
|
||||
public Set<String> getKeys() {
|
||||
return keys;
|
||||
}
|
||||
|
||||
private void checkKey(String key) throws IllegalArgumentException {
|
||||
if(!keys.contains(key)) throw new IllegalArgumentException("Not a valid key");
|
||||
}
|
||||
|
||||
public void set(String key, int value) throws IllegalArgumentException {
|
||||
checkKey(key);
|
||||
values.put(key, value);
|
||||
}
|
||||
|
||||
public int get(String key) throws IllegalArgumentException {
|
||||
checkKey(key);
|
||||
return values.getOrDefault(key, 0);
|
||||
}
|
||||
|
||||
public void reset(String key) throws IllegalArgumentException {
|
||||
checkKey(key);
|
||||
values.remove(key);
|
||||
}
|
||||
|
||||
public void increment(String key) throws IllegalArgumentException {
|
||||
checkKey(key);
|
||||
values.put(key, values.getOrDefault(key, 0) + 1);
|
||||
}
|
||||
|
||||
public void decrement(String key) throws IllegalArgumentException {
|
||||
checkKey(key);
|
||||
values.put(key, values.getOrDefault(key, 0) + 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return keys.stream()
|
||||
.map(key -> key + ": " + get(key))
|
||||
.collect(Collectors.joining("\n"));
|
||||
}
|
||||
|
||||
}
|
@ -2,27 +2,42 @@ package me.mrletsplay.shareclientcore.document;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import me.mrletsplay.shareclientcore.debug.DebugValues;
|
||||
|
||||
public class ArrayCharBag implements CharBag {
|
||||
|
||||
private List<Char> chars;
|
||||
|
||||
public ArrayCharBag() {
|
||||
this.chars = new ArrayList<>();
|
||||
}
|
||||
private DebugValues debugValues;
|
||||
|
||||
public ArrayCharBag(List<Char> chars) {
|
||||
this.chars = new ArrayList<>(chars);
|
||||
this.debugValues = new DebugValues(
|
||||
DEBUG_INSERTIONS,
|
||||
DEBUG_INSERTIONS_DROPPED,
|
||||
DEBUG_DELETIONS,
|
||||
DEBUG_DELETIONS_DROPPED
|
||||
);
|
||||
}
|
||||
|
||||
public ArrayCharBag() {
|
||||
this(Collections.emptyList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int add(Char character) {
|
||||
int i = 0;
|
||||
// TODO: use binary search
|
||||
while(i < chars.size() && Util.comparePositions(chars.get(i).position(), character.position()) < 0) i++;
|
||||
if(i < chars.size() && Util.comparePositions(chars.get(i).position(), character.position()) == 0) return -1;
|
||||
while(i < chars.size() && Util.compareChars(chars.get(i), character) < 0) i++;
|
||||
if(i < chars.size() && Util.compareChars(chars.get(i), character) == 0) {
|
||||
debugValues.increment(DEBUG_INSERTIONS_DROPPED);
|
||||
return -1;
|
||||
}
|
||||
chars.add(i, character);
|
||||
debugValues.increment(DEBUG_INSERTIONS);
|
||||
return i;
|
||||
}
|
||||
|
||||
@ -30,9 +45,13 @@ public class ArrayCharBag implements CharBag {
|
||||
public int remove(Char character) {
|
||||
int i = 0;
|
||||
// TODO: use binary search
|
||||
while(i < chars.size() && Util.comparePositions(chars.get(i).position(), character.position()) < 0) i++;
|
||||
if(i == chars.size() || Util.comparePositions(chars.get(i).position(), character.position()) != 0) return -1;
|
||||
while(i < chars.size() && Util.compareChars(chars.get(i), character) < 0) i++;
|
||||
if(i == chars.size() || Util.compareChars(chars.get(i), character) != 0) {
|
||||
debugValues.increment(DEBUG_DELETIONS_DROPPED);
|
||||
return -1;
|
||||
}
|
||||
chars.remove(i);
|
||||
debugValues.increment(DEBUG_DELETIONS);
|
||||
return i;
|
||||
}
|
||||
|
||||
@ -70,4 +89,9 @@ public class ArrayCharBag implements CharBag {
|
||||
return new String(getContents(), StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DebugValues getDebugValues() {
|
||||
return debugValues;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -3,8 +3,20 @@ package me.mrletsplay.shareclientcore.document;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
import me.mrletsplay.shareclientcore.debug.DebugValues;
|
||||
|
||||
public interface CharBag {
|
||||
|
||||
/**
|
||||
* Debug keys
|
||||
* @see #getDebugValues()
|
||||
*/
|
||||
public static final String
|
||||
DEBUG_INSERTIONS = "insertions",
|
||||
DEBUG_INSERTIONS_DROPPED = "insertionsDropped",
|
||||
DEBUG_DELETIONS = "deletions",
|
||||
DEBUG_DELETIONS_DROPPED = "deletionsDropped";
|
||||
|
||||
/**
|
||||
* Adds a character to the bag and returns the index it was inserted at, or -1 if it was not inserted because it already exists
|
||||
* @param character The character to add
|
||||
@ -39,7 +51,7 @@ public interface CharBag {
|
||||
|
||||
/**
|
||||
* Collects the chars in this bag ordered by their position into a list
|
||||
* @return
|
||||
* @return The chars in this bag
|
||||
*/
|
||||
public List<Char> toList();
|
||||
|
||||
@ -56,4 +68,13 @@ public interface CharBag {
|
||||
*/
|
||||
public String getContentsAsString();
|
||||
|
||||
/**
|
||||
* @return Debug information about this bag
|
||||
* @see #DEBUG_INSERTIONS
|
||||
* @see #DEBUG_INSERTIONS_DROPPED
|
||||
* @see #DEBUG_DELETIONS
|
||||
* @see #DEBUG_DELETIONS_DROPPED
|
||||
*/
|
||||
public DebugValues getDebugValues();
|
||||
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ public class SharedDocument implements MessageListener {
|
||||
if(index < 0 || index >= charBag.size() - 1) throw new IllegalArgumentException("Index out of bounds");
|
||||
|
||||
Char charBefore = charBag.get(index);
|
||||
Char charAfter = charBag.get(index +1);
|
||||
Char charAfter = charBag.get(index + 1);
|
||||
|
||||
Change[] changes = new Change[bytes.length];
|
||||
for(int i = 0; i < bytes.length; i++) {
|
||||
@ -113,7 +113,6 @@ public class SharedDocument implements MessageListener {
|
||||
if(charBag.remove(toRemove) == -1) throw new IllegalStateException("Couldn't remove existing char");
|
||||
}
|
||||
|
||||
|
||||
for(Change c : changes) {
|
||||
try {
|
||||
connection.send(new ChangeMessage(c));
|
||||
|
@ -19,6 +19,12 @@ public class Util {
|
||||
return Integer.compare(a.length, b.length);
|
||||
}
|
||||
|
||||
public static int compareChars(Char a, Char b) {
|
||||
int pos = comparePositions(a.position(), b.position());
|
||||
if(pos != 0) return pos;
|
||||
return Integer.compare(a.lamport(), b.lamport());
|
||||
}
|
||||
|
||||
public static Identifier[] generatePositionBetween(Identifier[] before, Identifier[] after, int site) {
|
||||
if(comparePositions(before, after) != -1) throw new IllegalArgumentException("before must be strictly less than after");
|
||||
|
||||
@ -28,7 +34,8 @@ public class Util {
|
||||
Identifier c1 = i >= before.length ? new Identifier(0, site) : before[i];
|
||||
Identifier c2 = i >= after.length ? new Identifier(BASE - 1, site) : after[i];
|
||||
|
||||
if(c1.digit() != c2.digit()) {
|
||||
// Modification to the original source because otherwise it can lead to cases where positions can't be generated and this seems to work
|
||||
if(i >= before.length || c1.digit() != c2.digit()) {
|
||||
int[] incremented = getIncremented(before, after, i);
|
||||
return constructPosition(incremented, before, after, site);
|
||||
}
|
||||
|
@ -63,40 +63,49 @@ public class SharingTest {
|
||||
String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890";
|
||||
|
||||
Random r = new Random(0);
|
||||
for(int i = 0; i < 1000; i++) {
|
||||
for(int i = 0; i < 10_000; i++) {
|
||||
switch(r.nextInt(sharedA.getContents().length == 0 ? 2 : 4)) {
|
||||
case 0: {
|
||||
// Insert A
|
||||
char[] insert = new char[r.nextInt(16)];
|
||||
for(int j = 0; j < insert.length; j++) insert[j] = chars.charAt(r.nextInt(chars.length()));
|
||||
sharedA.localInsert(r.nextInt(sharedA.getContents().length + 1), String.valueOf(insert));
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
// Insert B
|
||||
char[] insert = new char[r.nextInt(16)];
|
||||
for(int j = 0; j < insert.length; j++) insert[j] = chars.charAt(r.nextInt(chars.length()));
|
||||
sharedB.localInsert(r.nextInt(sharedB.getContents().length + 1), String.valueOf(insert));
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
// Delete A
|
||||
int len = sharedA.getContents().length;
|
||||
int idx = r.nextInt(len);
|
||||
int n = r.nextInt(len - idx);
|
||||
int n = r.nextInt(Math.min(16, len - idx)) + 1;
|
||||
sharedA.localDelete(idx, n);
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
// Delete B
|
||||
int len = sharedB.getContents().length;
|
||||
int idx = r.nextInt(len);
|
||||
int n = r.nextInt(len - idx + 1);
|
||||
int n = r.nextInt(Math.min(16, len - idx)) + 1;
|
||||
sharedB.localDelete(idx, n);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println("A: " + sharedA.getContentsAsString());
|
||||
System.out.println("B: " + sharedB.getContentsAsString());
|
||||
// System.out.println("A: " + sharedA.getContentsAsString());
|
||||
// System.out.println("B: " + sharedB.getContentsAsString());
|
||||
assertEquals(sharedA.getContentsAsString(), sharedB.getContentsAsString());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSharedDocumentInterleaving() {
|
||||
// TODO: test random interleaving of messages
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user