Skip to content

Commit

Permalink
Concurrency Improvements, new Delegates and general cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
JaciBrunning committed Apr 25, 2015
1 parent 5b44e1a commit 0959a78
Show file tree
Hide file tree
Showing 10 changed files with 236 additions and 19 deletions.
2 changes: 1 addition & 1 deletion build.settings
Original file line number Diff line number Diff line change
@@ -1 +1 @@
toast.version=1.1.0
toast.version=1.2.0
34 changes: 26 additions & 8 deletions src/main/java/jaci/openrio/toast/core/StateTracker.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,16 @@
import jaci.openrio.toast.core.loader.groovy.GroovyLoader;
import jaci.openrio.toast.core.loader.verification.VerificationWorker;
import jaci.openrio.toast.lib.FRCHooks;
import jaci.openrio.toast.lib.state.ConcurrentVector;
import jaci.openrio.toast.lib.state.RobotState;
import jaci.openrio.toast.lib.state.StateListener;

import java.util.List;
import java.util.Vector;

import static jaci.openrio.toast.lib.state.RobotState.*;

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

private static Toast impl;

private static volatile List<StateListener.Ticker> tickers = new Vector<StateListener.Ticker>();
private static volatile List<StateListener.Transition> transitioners = new Vector<StateListener.Transition>();
private static volatile ConcurrentVector<StateListener.Ticker> tickers = new ConcurrentVector<StateListener.Ticker>();
private static volatile ConcurrentVector<StateListener.Transition> transitioners = new ConcurrentVector<StateListener.Transition>();

/**
* Start the StateTracker loop
Expand Down Expand Up @@ -117,6 +115,8 @@ static void transition(RobotState state) {
lastState = currentState;
currentState = state;

transitioners.tick();

for (StateListener.Transition tra : transitioners)
tra.transitionState(currentState, lastState);

Expand All @@ -127,6 +127,8 @@ static void transition(RobotState state) {
* Tick all interfaces with the given state
*/
public static void tick(RobotState state) {
tickers.tick();

for (StateListener.Ticker ticker : tickers)
ticker.tickState(state);

Expand All @@ -139,15 +141,31 @@ public static void tick(RobotState state) {
* implementation
*/
public static void addTicker(StateListener.Ticker ticker) {
tickers.add(ticker);
tickers.addConcurrent(ticker);
}

/**
* Register a new 'Transition' {@link jaci.openrio.toast.lib.state.StateListener}. This will
* trigger whenever the robot switches between states.
*/
public static void addTransition(StateListener.Transition transition) {
transitioners.add(transition);
transitioners.addConcurrent(transition);
}

/**
* Remove a {@link jaci.openrio.toast.lib.state.StateListener.Ticker} from the
* StateTracker
*/
public static void removeTicker(StateListener.Ticker ticker) {
tickers.removeConcurrent(ticker);
}

/**
* Remove a {@link jaci.openrio.toast.lib.state.StateListener.Transition} from the
* StateTracker
*/
public static void removeTransition(StateListener.Transition transition) {
transitioners.removeConcurrent(transition);
}

private static boolean nextPeriodReady() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,35 @@
import jaci.openrio.toast.core.Toast;
import jaci.openrio.toast.core.command.FuzzyCommand;
import jaci.openrio.toast.core.shared.GlobalBlackboard;
import jaci.openrio.toast.core.thread.ToastThreadPool;

public class CommandGroovyScript extends FuzzyCommand {
@Override
public boolean shouldInvoke(String message) {
return message.startsWith("script");
return message.startsWith("script ") || message.startsWith("script -c");
}

@Override
public void invokeCommand(String message) {
String groovy = message.replaceFirst("script", "");
String groovy;
boolean concurrent = false;
if (message.startsWith("script -c")) {
groovy = message.replaceFirst("script -c", "");
concurrent = true;
} else groovy = message.replaceFirst("script", "");

Binding binding = new Binding();
binding.setVariable("_global", GlobalBlackboard.INSTANCE);
binding.setVariable("_toast", Toast.getToast());
GroovyShell shell = new GroovyShell(binding);
shell.evaluate(groovy);
if (concurrent)
ToastThreadPool.INSTANCE.addWorker(new Runnable() {
@Override
public void run() {
shell.evaluate(groovy);
}
});
else shell.evaluate(groovy);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.CopyOption;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;

public class CommandUSB extends AbstractCommand {
Expand Down
47 changes: 47 additions & 0 deletions src/main/java/jaci/openrio/toast/core/network/CommandDelegate.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package jaci.openrio.toast.core.network;

import jaci.openrio.delegate.BoundDelegate;
import jaci.openrio.toast.core.Toast;
import jaci.openrio.toast.core.command.CommandBus;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;

/**
* A one-way delegate for the Command Line. This Delegate will only receive messages and will interpret them as commands.
* 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}
*
* DelegateID: "TOAST_command"
*
* @author Jaci
*/
public class CommandDelegate implements BoundDelegate.ConnectionCallback {

static BoundDelegate server;

public static void init() {
server = SocketManager.register("TOAST_command");
CommandDelegate instance = new CommandDelegate();
server.callback(instance);
}

@Override
public void onClientConnect(Socket clientSocket, BoundDelegate delegate) {
new Thread() {
public void run() {
this.setName("CommandDelegate");
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
while (true)
CommandBus.parseMessage(reader.readLine());
} catch (IOException e) {
Toast.log().error("Could not read Command Client: ");
Toast.log().exception(e);
}
}
}.start();
}

}
92 changes: 92 additions & 0 deletions src/main/java/jaci/openrio/toast/core/network/LoggerDelegate.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package jaci.openrio.toast.core.network;

import jaci.openrio.delegate.BoundDelegate;
import jaci.openrio.toast.core.Toast;
import jaci.openrio.toast.lib.log.LogHandler;
import jaci.openrio.toast.lib.log.Logger;
import jaci.openrio.toast.lib.log.SysLogProxy;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.FileReader;
import java.net.Socket;
import java.util.Iterator;
import java.util.Vector;

/**
* A one-way delegate for the Command Line. This Delegate will only send messages that are a direct output from the Logger
* No data will be received from the client. Commands should be interpreted through the {@link jaci.openrio.toast.core.network.CommandDelegate}
*
* DelegateID: "TOAST_logger"
*
* @author Jaci
*/
public class LoggerDelegate implements BoundDelegate.ConnectionCallback, LogHandler {

static BoundDelegate server;
static Vector<Client> clients;

public static void init() {
server = SocketManager.register("TOAST_logger");
clients = new Vector<>();
LoggerDelegate instance = new LoggerDelegate();
server.callback(instance);
Logger.addHandler(instance);
}

/**
* Broadcast a message to all clients
*/
public static void broadcast(String message) {
Iterator<Client> it = clients.iterator();
while (it.hasNext()) {
Client client = it.next();
try {
if (!client.client.isClosed()) {
client.output.writeBytes(message + "\n");
continue;
}
} catch (Exception e) {}
it.remove();
}
}

@Override
public void onClientConnect(Socket clientSocket, BoundDelegate delegate) {
new Thread() {
public void run() {
this.setName("LoggerBacklog");
try {
BufferedReader file_reader = new BufferedReader(new FileReader(SysLogProxy.recentOut));
DataOutputStream socket_out = new DataOutputStream(clientSocket.getOutputStream());
String ln;
socket_out.writeBytes("***** BEGIN BACKLOG *****\n");
while ((ln = file_reader.readLine()) != null)
socket_out.writeBytes(ln + "\n");
socket_out.writeBytes("***** END BACKLOG *****\n");
file_reader.close();

Client client = new Client();
client.client = clientSocket;
client.output = socket_out;
clients.add(client);
} catch (java.io.IOException e) {
Toast.log().error("Could not connect Logger Client: ");
Toast.log().exception(e);
}
}
}.start();
}

@Override
public void onLog(String level, String message, String formatted, Logger logger) {
broadcast(formatted);
}

public static class Client {

public DataOutputStream output;
public Socket client;

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ public class SocketManager {

static boolean launch = false;

/**
* Register the default BoundDelegates used by Toast
*/
public static void registerNatives() {
LoggerDelegate.init();
CommandDelegate.init();
}

/**
* Register your own Delegate. The String 'id' parameter should be your module name, prepended
* by your package name. This is to make sure there are no conflicts. e.g. 'com.yourname.YourModule'
Expand All @@ -34,6 +42,7 @@ public static BoundDelegate register(String id) {
* Launch the DelegateServer. This should be left to Toast to handle.
*/
public static void launch() {
SocketManager.registerNatives();
if (launch) {
new Thread() {
public void run() {
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/jaci/openrio/toast/lib/log/LogHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ public interface LogHandler {
/**
* Called after a message is logged. Use this to send data to your driver-station or whatever else
*/
public void onLog(String level, String message);
public void onLog(String level, String message, String formatted, Logger logger);

}
5 changes: 1 addition & 4 deletions src/main/java/jaci/openrio/toast/lib/log/Logger.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ public class Logger {

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

public static Vector<String> backlog = new Vector<String>();
public static Vector<LogHandler> handlers = new Vector<LogHandler>();

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

ps.println(ts);

backlog.add(ts);

for (LogHandler hand : handlers)
hand.onLog(level, message);
hand.onLog(level, message, ts, this);
}

public String getPrefix(String level) {
Expand Down
42 changes: 42 additions & 0 deletions src/main/java/jaci/openrio/toast/lib/state/ConcurrentVector.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package jaci.openrio.toast.lib.state;

import java.util.Iterator;
import java.util.Vector;

/**
* Why the extra Join and Remove Queues you ask? Well, if a module that is currently in a ticking or transition state wants
* to add or remove a listener from the list, we run into some errors. Without 'synchronized', we run into a Concurrent
* Modification, which causes big issues. With 'synchronized', the Thread will be forever blocked because it's trying to access
* a synchronized lock from INSIDE the synchronized method. I hate iteration iteration iteration iteration iteration iteration
* iteration iteration iteration iteration iteration iteration iteration....
*
* @author Jaci
*/
public class ConcurrentVector<E> extends Vector<E> {

Vector<E> joinQueue = new Vector<E>();
Vector<E> removeQueue = new Vector<E>();

public void addConcurrent(E element) {
joinQueue.add(element);
}

public void removeConcurrent(E element) {
removeQueue.add(element);
}

public synchronized void tick() {
Iterator<E> joinIt = joinQueue.iterator();
while (joinIt.hasNext()) {
this.add(joinIt.next());
joinIt.remove();
}

Iterator<E> removeIt = removeQueue.iterator();
while (removeIt.hasNext()) {
this.remove(removeIt.next());
removeIt.remove();
}
}

}

0 comments on commit 0959a78

Please sign in to comment.