Skip to content

Commit c895f07

Browse files
committed
Adds Dynamic Typed Operations Valve & Switch
Adds two operations to allow for control flow of data in a GRIP application. - Switch allows you to switch between to arbatrarily typed inputs - Valve can stop the flow of data through a connection for an arbatrarily typed input
1 parent 3ae952b commit c895f07

21 files changed

+566
-104
lines changed

core/src/main/java/edu/wpi/grip/core/Connection.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,21 +20,21 @@ public class Connection<T> {
2020
private final InputSocket<T> inputSocket;
2121

2222

23-
public interface Factory <T> {
23+
public interface Factory<T> {
2424
Connection<T> create(OutputSocket<? extends T> outputSocket, InputSocket<T> inputSocket);
2525
}
2626

2727
/**
28-
* @param pipeline The pipeline to create the connection inside of.
29-
* @param outputSocket The socket to listen for changes in.
30-
* @param inputSocket A different socket to update when a change occurs in the first.
28+
* @param connectionValidator An object to validate that the connection can be made
29+
* @param outputSocket The socket to listen for changes in.
30+
* @param inputSocket A different socket to update when a change occurs in the first.
3131
*/
3232
@Inject
33-
Connection(EventBus eventBus, Pipeline pipeline, @Assisted OutputSocket<? extends T> outputSocket, @Assisted InputSocket<T> inputSocket) {
33+
Connection(EventBus eventBus, ConnectionValidator connectionValidator, @Assisted OutputSocket<? extends T> outputSocket, @Assisted InputSocket<T> inputSocket) {
3434
this.eventBus = eventBus;
3535
this.outputSocket = outputSocket;
3636
this.inputSocket = inputSocket;
37-
checkArgument(pipeline.canConnect(outputSocket, inputSocket), "Cannot connect sockets");
37+
checkArgument(connectionValidator.canConnect(outputSocket, inputSocket), "Cannot connect sockets");
3838
}
3939

4040
public OutputSocket<? extends T> getOutputSocket() {
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package edu.wpi.grip.core;
2+
3+
/**
4+
* Allows {@link Connection Connections} to be validated to ensure that the pipeline will remain valid.
5+
*/
6+
public interface ConnectionValidator {
7+
8+
/**
9+
* Resolves which {@link Socket} is the {@link OutputSocket} and which is the {@link InputSocket}
10+
* and calls {@link #canConnect(OutputSocket, InputSocket)}
11+
* @param socket1 The first socket
12+
* @param socket2 The second socket
13+
* @return The return value of {@link #canConnect(OutputSocket, InputSocket)}
14+
*/
15+
default boolean canConnect(Socket socket1, Socket socket2) {
16+
final OutputSocket<?> outputSocket;
17+
final InputSocket<?> inputSocket;
18+
19+
// One socket must be an input and one must be an output
20+
if (socket1.getDirection() == socket2.getDirection()) {
21+
return false;
22+
}
23+
24+
if (socket1.getDirection().equals(Socket.Direction.OUTPUT)) {
25+
outputSocket = (OutputSocket) socket1;
26+
inputSocket = (InputSocket) socket2;
27+
} else {
28+
inputSocket = (InputSocket) socket1;
29+
outputSocket = (OutputSocket) socket2;
30+
}
31+
32+
// DO NOT DO ANY OTHER SORT OF VALIDATION HERE. We just want to resolve the types.
33+
34+
return canConnect(outputSocket, inputSocket);
35+
}
36+
37+
/**
38+
* Determines if an output socket can be connected to an input socket
39+
* @param outputSocket The output socket to connect to the input socket
40+
* @param inputSocket The input socket to accept the output value of the output socket
41+
* @return True if a valid connection can be made from these two Sockets
42+
*/
43+
boolean canConnect(OutputSocket<?> outputSocket, InputSocket<?> inputSocket);
44+
}

core/src/main/java/edu/wpi/grip/core/GRIPCoreModule.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ public <I> void hear(TypeLiteral<I> type, TypeEncounter<I> encounter) {
103103
install(new FactoryModuleBuilder().build(new TypeLiteral<Connection.Factory<Object>>() {
104104
}));
105105

106-
106+
bind(ConnectionValidator.class).to(Pipeline.class);
107107
bind(Source.SourceFactory.class).to(Source.SourceFactoryImpl.class);
108108
bind(CameraSource.FrameGrabberFactory.class).to(CameraSource.FrameGrabberFactoryImpl.class);
109109
install(new FactoryModuleBuilder()
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package edu.wpi.grip.core;
2+
3+
import com.google.common.eventbus.EventBus;
4+
import edu.wpi.grip.core.events.ConnectionRemovedEvent;
5+
6+
import java.util.HashSet;
7+
import java.util.Optional;
8+
import java.util.Set;
9+
10+
import static com.google.common.base.Preconditions.checkNotNull;
11+
12+
/**
13+
* A {@link SocketHint} that's type is linked between many other sockets and who's type is defined by
14+
* whatever {@link InputSocket} was connected to it first.
15+
*/
16+
public final class LinkedSocketHint extends SocketHint.SocketHintDecorator {
17+
/**
18+
* Keeps track of the sockets that control the type of this socket hint
19+
*/
20+
private final Set<InputSocket> controllingSockets = new HashSet<>();
21+
private final Set<OutputSocket> controlledOutputSockets = new HashSet<>();
22+
private final EventBus eventBus;
23+
private Optional<Class> connectedType = Optional.empty();
24+
25+
@SuppressWarnings("unchecked")
26+
public LinkedSocketHint(EventBus eventBus) {
27+
super(new Builder<>(Object.class).identifier("").build());
28+
this.eventBus = checkNotNull(eventBus, "EventBus cannot be null");
29+
}
30+
31+
/**
32+
* Creates an {@link InputSocket} that is linked to this SocketHint
33+
*
34+
* @param hintIdentifier The identifier for this socket's SocketHint
35+
* @return A socket hint that's socket type is determined by this SocketHint
36+
*/
37+
@SuppressWarnings("unchecked")
38+
public InputSocket linkedInputSocket(String hintIdentifier) {
39+
// Our own custom implementation of socket hint that interacts on this class when connections are
40+
// added and removed
41+
return new InputSocket(eventBus, new IdentiferOverridingSocketHintDecorator(this, hintIdentifier)) {
42+
@Override
43+
public void addConnection(Connection connection) {
44+
synchronized (this) {
45+
controllingSockets.add(this);
46+
connectedType = Optional.of(connection.getOutputSocket().getSocketHint().getType());
47+
}
48+
super.addConnection(connection);
49+
}
50+
51+
@Override
52+
public void onDisconnected() {
53+
synchronized (this) {
54+
// Remove this socket because it is no longer controlling the type of socket
55+
controllingSockets.remove(this);
56+
if (controllingSockets.isEmpty()) { // When the set is empty, the socket can support any type again
57+
connectedType = Optional.empty();
58+
// XXX: TODO: This is breaking the law of Demeter fix this
59+
controlledOutputSockets.forEach(outputSocket -> {
60+
final Set<Connection<?>> connections = outputSocket.getConnections();
61+
connections.stream().map(ConnectionRemovedEvent::new).forEach(this.eventBus::post);
62+
outputSocket.setPreviewed(false);
63+
outputSocket.setValueOptional(Optional.empty());
64+
});
65+
}
66+
}
67+
super.onDisconnected();
68+
}
69+
};
70+
}
71+
72+
/**
73+
* Creates an input socket that is linked to this SocketHint.
74+
* This output socket will automatically be disconnected when there is no longer an input socket to guarantee the type
75+
* of this SocketHint
76+
*
77+
* @param hintIdentifier The identifier for this socket's SocketHint
78+
* @return An OutputSocket that's type is dynamically linked to this SocketHint
79+
*/
80+
@SuppressWarnings("unchecked")
81+
public OutputSocket linkedOutputSocket(String hintIdentifier) {
82+
final OutputSocket outSocket = new OutputSocket(eventBus, new IdentiferOverridingSocketHintDecorator(this, hintIdentifier));
83+
controlledOutputSockets.add(outSocket);
84+
return outSocket;
85+
}
86+
87+
@Override
88+
public String getTypeLabel() {
89+
return "<Generic>";
90+
}
91+
92+
@Override
93+
public Class getType() {
94+
// If the type is known because one of the input sockets is connected then return that. Otherwise, return Object
95+
return connectedType.orElse(Object.class);
96+
}
97+
98+
@Override
99+
@SuppressWarnings("unchecked")
100+
public boolean isCompatibleWith(SocketHint other) {
101+
if (connectedType.isPresent()) { // If the type is present
102+
// Then use this socket hint to determine if this socket can be connected
103+
return connectedType.get().isAssignableFrom(other.getType());
104+
} else {
105+
// Otherwise use the socket hint we are decorating to determine the supported type
106+
return getDecorated().isCompatibleWith(other);
107+
}
108+
}
109+
}

core/src/main/java/edu/wpi/grip/core/Operation.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ enum Category {
1616
IMAGE_PROCESSING,
1717
FEATURE_DETECTION,
1818
NETWORK,
19+
LOGICAL,
1920
OPENCV,
2021
MISCELLANEOUS,
2122
}
@@ -53,6 +54,9 @@ default Optional<InputStream> getIcon() {
5354
return Optional.empty();
5455
}
5556

57+
default SocketsProvider createSockets(EventBus eventBus) {
58+
return new SocketsProvider(createInputSockets(eventBus), createOutputSockets(eventBus));
59+
}
5660
/**
5761
* @param eventBus The Guava {@link EventBus} used by the application.
5862
* @return An array of sockets for the inputs that the operation expects.

core/src/main/java/edu/wpi/grip/core/OutputSocket.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import edu.wpi.grip.core.events.SocketPreviewChangedEvent;
88

99
import javax.annotation.Nullable;
10-
import java.util.NoSuchElementException;
1110
import java.util.Optional;
1211

1312
/**
@@ -29,7 +28,6 @@ public class OutputSocket<T> extends Socket<T> {
2928
*/
3029
public OutputSocket(EventBus eventBus, SocketHint<T> socketHint) {
3130
super(eventBus, socketHint, Direction.OUTPUT);
32-
getValue().orElseThrow(()-> new NoSuchElementException("The SocketHint for an output socket must have an initial value to be valid"));
3331
}
3432

3533
@Override

core/src/main/java/edu/wpi/grip/core/Pipeline.java

Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
*/
3131
@Singleton
3232
@XStreamAlias(value = "grip:Pipeline")
33-
public class Pipeline {
33+
public class Pipeline implements ConnectionValidator {
3434

3535
@Inject
3636
@XStreamOmitField
@@ -176,28 +176,13 @@ public ProjectSettings getProjectSettings() {
176176
* @return true if a connection can be made from the given output socket to the given input socket
177177
*/
178178
@SuppressWarnings("unchecked")
179-
public boolean canConnect(Socket socket1, Socket socket2) {
180-
final OutputSocket<?> outputSocket;
181-
final InputSocket<?> inputSocket;
182-
183-
// One socket must be an input and one must be an output
184-
if (socket1.getDirection() == socket2.getDirection()) {
185-
return false;
186-
}
187-
188-
if (socket1.getDirection().equals(Socket.Direction.OUTPUT)) {
189-
outputSocket = (OutputSocket) socket1;
190-
inputSocket = (InputSocket) socket2;
191-
} else {
192-
inputSocket = (InputSocket) socket1;
193-
outputSocket = (OutputSocket) socket2;
194-
}
195-
196-
final SocketHint outputHint = socket1.getSocketHint();
197-
final SocketHint inputHint = socket2.getSocketHint();
179+
@Override
180+
public boolean canConnect(OutputSocket<?> outputSocket, InputSocket<?> inputSocket) {
181+
final SocketHint outputHint = outputSocket.getSocketHint();
182+
final SocketHint inputHint = inputSocket.getSocketHint();
198183

199184
// The input socket must be able to hold the type of value that the output socket contains
200-
if (!inputHint.getType().isAssignableFrom(outputHint.getType())) {
185+
if (!inputHint.isCompatibleWith(outputHint)) {
201186
return false;
202187
}
203188

core/src/main/java/edu/wpi/grip/core/Socket.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public enum Direction {INPUT, OUTPUT}
3232
private final Direction direction;
3333
private final Set<Connection> connections = new HashSet<>();
3434
private final SocketHint<T> socketHint;
35-
private Optional<? extends T> value;
35+
private Optional<? extends T> value = Optional.empty();
3636

3737

3838
/**
@@ -43,7 +43,6 @@ public enum Direction {INPUT, OUTPUT}
4343
public Socket(EventBus eventBus, SocketHint<T> socketHint, Direction direction) {
4444
this.eventBus = checkNotNull(eventBus, "EventBus can not be null");
4545
this.socketHint = checkNotNull(socketHint, "Socket Hint can not be null");
46-
this.value = socketHint.createInitialValue();
4746
this.direction = checkNotNull(direction, "Direction can not be null");
4847
}
4948

@@ -82,6 +81,9 @@ public void setValue(@Nullable T value) {
8281
* @return The value currently stored in this socket.
8382
*/
8483
public Optional<T> getValue() {
84+
if(!this.value.isPresent()) {
85+
this.value = socketHint.createInitialValue();
86+
}
8587
return (Optional<T>) this.value;
8688
}
8789

0 commit comments

Comments
 (0)