Skip to content

Commit 0959a78

Browse files
committed
Concurrency Improvements, new Delegates and general cleanup
1 parent 5b44e1a commit 0959a78

10 files changed

Lines changed: 236 additions & 19 deletions

File tree

build.settings

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
toast.version=1.1.0
1+
toast.version=1.2.0

src/main/java/jaci/openrio/toast/core/StateTracker.java

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,16 @@
55
import jaci.openrio.toast.core.loader.groovy.GroovyLoader;
66
import jaci.openrio.toast.core.loader.verification.VerificationWorker;
77
import jaci.openrio.toast.lib.FRCHooks;
8+
import jaci.openrio.toast.lib.state.ConcurrentVector;
89
import jaci.openrio.toast.lib.state.RobotState;
910
import jaci.openrio.toast.lib.state.StateListener;
1011

11-
import java.util.List;
12-
import java.util.Vector;
13-
1412
import static jaci.openrio.toast.lib.state.RobotState.*;
1513

1614
/**
1715
* Keeps track of the {@link jaci.openrio.toast.lib.state.RobotState} the robot is in, as well as the one
1816
* it just switched from. This allows for context-aware state management.
19-
*
17+
* <p>
2018
* This class also allows classes to implement sub-interfaces of {@link jaci.openrio.toast.lib.state.StateListener},
2119
* which will trigger the interfaces when the robot 'ticks' or transitions between states. This allows for multiple
2220
* handlers to work with states
@@ -35,8 +33,8 @@ public class StateTracker {
3533

3634
private static Toast impl;
3735

38-
private static volatile List<StateListener.Ticker> tickers = new Vector<StateListener.Ticker>();
39-
private static volatile List<StateListener.Transition> transitioners = new Vector<StateListener.Transition>();
36+
private static volatile ConcurrentVector<StateListener.Ticker> tickers = new ConcurrentVector<StateListener.Ticker>();
37+
private static volatile ConcurrentVector<StateListener.Transition> transitioners = new ConcurrentVector<StateListener.Transition>();
4038

4139
/**
4240
* Start the StateTracker loop
@@ -117,6 +115,8 @@ static void transition(RobotState state) {
117115
lastState = currentState;
118116
currentState = state;
119117

118+
transitioners.tick();
119+
120120
for (StateListener.Transition tra : transitioners)
121121
tra.transitionState(currentState, lastState);
122122

@@ -127,6 +127,8 @@ static void transition(RobotState state) {
127127
* Tick all interfaces with the given state
128128
*/
129129
public static void tick(RobotState state) {
130+
tickers.tick();
131+
130132
for (StateListener.Ticker ticker : tickers)
131133
ticker.tickState(state);
132134

@@ -139,15 +141,31 @@ public static void tick(RobotState state) {
139141
* implementation
140142
*/
141143
public static void addTicker(StateListener.Ticker ticker) {
142-
tickers.add(ticker);
144+
tickers.addConcurrent(ticker);
143145
}
144146

145147
/**
146148
* Register a new 'Transition' {@link jaci.openrio.toast.lib.state.StateListener}. This will
147149
* trigger whenever the robot switches between states.
148150
*/
149151
public static void addTransition(StateListener.Transition transition) {
150-
transitioners.add(transition);
152+
transitioners.addConcurrent(transition);
153+
}
154+
155+
/**
156+
* Remove a {@link jaci.openrio.toast.lib.state.StateListener.Ticker} from the
157+
* StateTracker
158+
*/
159+
public static void removeTicker(StateListener.Ticker ticker) {
160+
tickers.removeConcurrent(ticker);
161+
}
162+
163+
/**
164+
* Remove a {@link jaci.openrio.toast.lib.state.StateListener.Transition} from the
165+
* StateTracker
166+
*/
167+
public static void removeTransition(StateListener.Transition transition) {
168+
transitioners.removeConcurrent(transition);
151169
}
152170

153171
private static boolean nextPeriodReady() {

src/main/java/jaci/openrio/toast/core/command/cmd/CommandGroovyScript.java

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,35 @@
55
import jaci.openrio.toast.core.Toast;
66
import jaci.openrio.toast.core.command.FuzzyCommand;
77
import jaci.openrio.toast.core.shared.GlobalBlackboard;
8+
import jaci.openrio.toast.core.thread.ToastThreadPool;
89

910
public class CommandGroovyScript extends FuzzyCommand {
1011
@Override
1112
public boolean shouldInvoke(String message) {
12-
return message.startsWith("script");
13+
return message.startsWith("script ") || message.startsWith("script -c");
1314
}
1415

1516
@Override
1617
public void invokeCommand(String message) {
17-
String groovy = message.replaceFirst("script", "");
18+
String groovy;
19+
boolean concurrent = false;
20+
if (message.startsWith("script -c")) {
21+
groovy = message.replaceFirst("script -c", "");
22+
concurrent = true;
23+
} else groovy = message.replaceFirst("script", "");
24+
1825
Binding binding = new Binding();
1926
binding.setVariable("_global", GlobalBlackboard.INSTANCE);
2027
binding.setVariable("_toast", Toast.getToast());
2128
GroovyShell shell = new GroovyShell(binding);
22-
shell.evaluate(groovy);
29+
if (concurrent)
30+
ToastThreadPool.INSTANCE.addWorker(new Runnable() {
31+
@Override
32+
public void run() {
33+
shell.evaluate(groovy);
34+
}
35+
});
36+
else shell.evaluate(groovy);
2337
}
2438

2539
}

src/main/java/jaci/openrio/toast/core/command/cmd/CommandUSB.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,7 @@
1010
import java.io.FileInputStream;
1111
import java.io.FileOutputStream;
1212
import java.io.IOException;
13-
import java.nio.file.CopyOption;
1413
import java.nio.file.Files;
15-
import java.nio.file.Path;
1614
import java.nio.file.StandardCopyOption;
1715

1816
public class CommandUSB extends AbstractCommand {
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package jaci.openrio.toast.core.network;
2+
3+
import jaci.openrio.delegate.BoundDelegate;
4+
import jaci.openrio.toast.core.Toast;
5+
import jaci.openrio.toast.core.command.CommandBus;
6+
7+
import java.io.BufferedReader;
8+
import java.io.IOException;
9+
import java.io.InputStreamReader;
10+
import java.net.Socket;
11+
12+
/**
13+
* A one-way delegate for the Command Line. This Delegate will only receive messages and will interpret them as commands.
14+
* No data will be sent to the client from this delegate. To read the output log, it's recommended to use the {@link jaci.openrio.toast.core.network.LoggerDelegate}
15+
*
16+
* DelegateID: "TOAST_command"
17+
*
18+
* @author Jaci
19+
*/
20+
public class CommandDelegate implements BoundDelegate.ConnectionCallback {
21+
22+
static BoundDelegate server;
23+
24+
public static void init() {
25+
server = SocketManager.register("TOAST_command");
26+
CommandDelegate instance = new CommandDelegate();
27+
server.callback(instance);
28+
}
29+
30+
@Override
31+
public void onClientConnect(Socket clientSocket, BoundDelegate delegate) {
32+
new Thread() {
33+
public void run() {
34+
this.setName("CommandDelegate");
35+
try {
36+
BufferedReader reader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
37+
while (true)
38+
CommandBus.parseMessage(reader.readLine());
39+
} catch (IOException e) {
40+
Toast.log().error("Could not read Command Client: ");
41+
Toast.log().exception(e);
42+
}
43+
}
44+
}.start();
45+
}
46+
47+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package jaci.openrio.toast.core.network;
2+
3+
import jaci.openrio.delegate.BoundDelegate;
4+
import jaci.openrio.toast.core.Toast;
5+
import jaci.openrio.toast.lib.log.LogHandler;
6+
import jaci.openrio.toast.lib.log.Logger;
7+
import jaci.openrio.toast.lib.log.SysLogProxy;
8+
9+
import java.io.BufferedReader;
10+
import java.io.DataOutputStream;
11+
import java.io.FileReader;
12+
import java.net.Socket;
13+
import java.util.Iterator;
14+
import java.util.Vector;
15+
16+
/**
17+
* A one-way delegate for the Command Line. This Delegate will only send messages that are a direct output from the Logger
18+
* No data will be received from the client. Commands should be interpreted through the {@link jaci.openrio.toast.core.network.CommandDelegate}
19+
*
20+
* DelegateID: "TOAST_logger"
21+
*
22+
* @author Jaci
23+
*/
24+
public class LoggerDelegate implements BoundDelegate.ConnectionCallback, LogHandler {
25+
26+
static BoundDelegate server;
27+
static Vector<Client> clients;
28+
29+
public static void init() {
30+
server = SocketManager.register("TOAST_logger");
31+
clients = new Vector<>();
32+
LoggerDelegate instance = new LoggerDelegate();
33+
server.callback(instance);
34+
Logger.addHandler(instance);
35+
}
36+
37+
/**
38+
* Broadcast a message to all clients
39+
*/
40+
public static void broadcast(String message) {
41+
Iterator<Client> it = clients.iterator();
42+
while (it.hasNext()) {
43+
Client client = it.next();
44+
try {
45+
if (!client.client.isClosed()) {
46+
client.output.writeBytes(message + "\n");
47+
continue;
48+
}
49+
} catch (Exception e) {}
50+
it.remove();
51+
}
52+
}
53+
54+
@Override
55+
public void onClientConnect(Socket clientSocket, BoundDelegate delegate) {
56+
new Thread() {
57+
public void run() {
58+
this.setName("LoggerBacklog");
59+
try {
60+
BufferedReader file_reader = new BufferedReader(new FileReader(SysLogProxy.recentOut));
61+
DataOutputStream socket_out = new DataOutputStream(clientSocket.getOutputStream());
62+
String ln;
63+
socket_out.writeBytes("***** BEGIN BACKLOG *****\n");
64+
while ((ln = file_reader.readLine()) != null)
65+
socket_out.writeBytes(ln + "\n");
66+
socket_out.writeBytes("***** END BACKLOG *****\n");
67+
file_reader.close();
68+
69+
Client client = new Client();
70+
client.client = clientSocket;
71+
client.output = socket_out;
72+
clients.add(client);
73+
} catch (java.io.IOException e) {
74+
Toast.log().error("Could not connect Logger Client: ");
75+
Toast.log().exception(e);
76+
}
77+
}
78+
}.start();
79+
}
80+
81+
@Override
82+
public void onLog(String level, String message, String formatted, Logger logger) {
83+
broadcast(formatted);
84+
}
85+
86+
public static class Client {
87+
88+
public DataOutputStream output;
89+
public Socket client;
90+
91+
}
92+
}

src/main/java/jaci/openrio/toast/core/network/SocketManager.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,14 @@ public class SocketManager {
2121

2222
static boolean launch = false;
2323

24+
/**
25+
* Register the default BoundDelegates used by Toast
26+
*/
27+
public static void registerNatives() {
28+
LoggerDelegate.init();
29+
CommandDelegate.init();
30+
}
31+
2432
/**
2533
* Register your own Delegate. The String 'id' parameter should be your module name, prepended
2634
* by your package name. This is to make sure there are no conflicts. e.g. 'com.yourname.YourModule'
@@ -34,6 +42,7 @@ public static BoundDelegate register(String id) {
3442
* Launch the DelegateServer. This should be left to Toast to handle.
3543
*/
3644
public static void launch() {
45+
SocketManager.registerNatives();
3746
if (launch) {
3847
new Thread() {
3948
public void run() {

src/main/java/jaci/openrio/toast/lib/log/LogHandler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,6 @@ public interface LogHandler {
1212
/**
1313
* Called after a message is logged. Use this to send data to your driver-station or whatever else
1414
*/
15-
public void onLog(String level, String message);
15+
public void onLog(String level, String message, String formatted, Logger logger);
1616

1717
}

src/main/java/jaci/openrio/toast/lib/log/Logger.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ public class Logger {
3030

3131
public DateFormat dateFormat = new SimpleDateFormat("dd/MM/yy-hh:mm:ss");
3232

33-
public static Vector<String> backlog = new Vector<String>();
3433
public static Vector<LogHandler> handlers = new Vector<LogHandler>();
3534

3635
int attr;
@@ -71,10 +70,8 @@ private void log(String message, String level, PrintStream ps) {
7170

7271
ps.println(ts);
7372

74-
backlog.add(ts);
75-
7673
for (LogHandler hand : handlers)
77-
hand.onLog(level, message);
74+
hand.onLog(level, message, ts, this);
7875
}
7976

8077
public String getPrefix(String level) {
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package jaci.openrio.toast.lib.state;
2+
3+
import java.util.Iterator;
4+
import java.util.Vector;
5+
6+
/**
7+
* Why the extra Join and Remove Queues you ask? Well, if a module that is currently in a ticking or transition state wants
8+
* to add or remove a listener from the list, we run into some errors. Without 'synchronized', we run into a Concurrent
9+
* Modification, which causes big issues. With 'synchronized', the Thread will be forever blocked because it's trying to access
10+
* a synchronized lock from INSIDE the synchronized method. I hate iteration iteration iteration iteration iteration iteration
11+
* iteration iteration iteration iteration iteration iteration iteration....
12+
*
13+
* @author Jaci
14+
*/
15+
public class ConcurrentVector<E> extends Vector<E> {
16+
17+
Vector<E> joinQueue = new Vector<E>();
18+
Vector<E> removeQueue = new Vector<E>();
19+
20+
public void addConcurrent(E element) {
21+
joinQueue.add(element);
22+
}
23+
24+
public void removeConcurrent(E element) {
25+
removeQueue.add(element);
26+
}
27+
28+
public synchronized void tick() {
29+
Iterator<E> joinIt = joinQueue.iterator();
30+
while (joinIt.hasNext()) {
31+
this.add(joinIt.next());
32+
joinIt.remove();
33+
}
34+
35+
Iterator<E> removeIt = removeQueue.iterator();
36+
while (removeIt.hasNext()) {
37+
this.remove(removeIt.next());
38+
removeIt.remove();
39+
}
40+
}
41+
42+
}

0 commit comments

Comments
 (0)