// Copyright 2011-2012 Paulo Augusto Peccin. See licence.txt distributed with this file.
package org.javatari.atari.network;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.javatari.atari.cartridge.Cartridge;
import org.javatari.atari.console.Console;
import org.javatari.atari.console.savestate.ConsoleState;
import org.javatari.atari.console.savestate.SaveStateMedia;
import org.javatari.atari.controls.ConsoleControlsSocket;
import org.javatari.atari.controls.ConsoleControls.Control;
import org.javatari.general.board.ClockDriven;
public final class ClientConsole extends Console implements ClockDriven {
public ClientConsole(RemoteReceiver receiver) {
super();
setupReceiver(receiver);
}
public RemoteReceiver remoteReceiver() {
return remoteReceiver;
}
@Override
public synchronized void go() {
// Clock is controlled remotely, but signal unpause to unblock clock pulses
isPaused = false;
}
@Override
public synchronized void pause() {
// Clock is controlled remotely, but signal pause to block clock pulses
isPaused = true;
}
@Override
public void extendedPowerOff() {
try {
remoteReceiver.disconnect();
} catch (IOException e) {
// Ignore
}
super.extendedPowerOff();
}
@Override
protected void mainClockCreate() {
// Ignore, the clock is controlled remotely
}
@Override
protected void mainClockAdjustToNormal() {
// Ignore, the clock is controlled remotely
}
@Override
protected void mainClockAdjustToAlternate() {
// Ignore, the clock is controlled remotely
}
@Override
protected void mainClockDestroy() {
// Ignore, the clock is controlled remotely
}
@Override
protected void socketsCreate() {
controlsSocket = new ClientConsoleControlsSocketAdapter();
controlsSocket.addForwardedInput(new ConsoleControlsInputAdapter());
controlsSocket.addForwardedInput(tia);
controlsSocket.addForwardedInput(pia);
cartridgeSocket = new ClientConsoleCartridgeSocketAdapter();
saveStateSocket = new ClientConsoleSaveStateSocketAdapter();
}
@Override
public void clockPulse() {
// Block clock pulses until Console is unpaused (go) is turned off
// Important while Screen is dettached in Applet mode,
// so no indeterministic behavior is introduced while the Screen is off!
while(isPaused && powerOn) try {
Thread.sleep(1000/120); // Half a frame
} catch (InterruptedException e) {}
synchronized(this) {
tia.clockPulse();
}
}
@Override
protected void cycleCartridgeFormat() {
// Ignore, the new Cartridge state will come from the Server
}
@Override
protected void powerFry() {
// Ignore, the new RAM state will come from the Server
}
void connected() {
showOSD("Connected to Player 1 Server", true);
}
void disconnected(){
powerOff();
showOSD("Disconnected from Player 1 Server", true);
}
void receiveServerUpdate(ServerUpdate update) {
if (update.powerChange != null)
receiveServerPower(update.powerChange);
if (update.consoleState != null)
receiveServerState(update.consoleState );
if (update.controlChanges != null)
((ClientConsoleControlsSocketAdapter) controlsSocket).serverControlChanges(update.controlChanges);
if (powerOn)
clockPulse();
}
List<ControlChange> controlChangesToSend() {
return ((ClientConsoleControlsSocketAdapter) controlsSocket).getChangesToSend();
}
private void receiveServerPower(boolean serverPowerOn) {
if (serverPowerOn && !powerOn) powerOn();
else if (!serverPowerOn && powerOn) powerOff();
}
private void receiveServerState(ConsoleState state) {
loadState(state);
}
private void setupReceiver(RemoteReceiver receiver) {
remoteReceiver = receiver;
remoteReceiver.clientConsole(this);
}
private RemoteReceiver remoteReceiver;
private boolean isPaused = false; // To control the pause/go mechanism
private class ClientConsoleControlsSocketAdapter extends ConsoleControlsSocket {
@Override
public void controlStateChanged(Control control, boolean state) {
if (DISABLED_CONTROLS.contains(control)) {
showOSD("This function is only available on the Server", true);
return;
}
synchronized (queuedChanges) {
queuedChanges.add(new ControlChange(control, state));
}
}
@Override
public void controlStateChanged(Control control, int position) {
synchronized (queuedChanges) {
queuedChanges.add(new ControlChangeForPaddle(control, position));
}
}
public List<ControlChange> getChangesToSend() {
List<ControlChange> changesToSend;
synchronized (queuedChanges) {
if (queuedChanges.isEmpty())
return null;
else {
changesToSend = new ArrayList<ControlChange>(queuedChanges);
queuedChanges.clear();
return changesToSend;
}
}
}
public void serverControlChanges(List<ControlChange> changes) {
// Effectively accepts the control changes, received from the server
for (ControlChange change : changes)
if (change instanceof ControlChangeForPaddle)
super.controlStateChanged(change.control, ((ControlChangeForPaddle)change).position);
else
super.controlStateChanged(change.control, change.state);
}
private List<ControlChange> queuedChanges = new ArrayList<ControlChange>();
}
// Cartridge insertion is controlled only by the Server
private class ClientConsoleCartridgeSocketAdapter extends CartridgeSocketAdapter {
@Override
public void insert(Cartridge cartridge, boolean autoPower) {
showOSD("Only the Server can change Cartridges", true);
}
}
private class ClientConsoleSaveStateSocketAdapter extends SaveStateSocketAdapter {
@Override
public void connectMedia(SaveStateMedia media) {
// Ignore, savestates are controlled by the Server
}
@Override
public void connectCartridge(Cartridge cartridge) {
// No socket, savestates are controlled by the Server
cartridge.connectSaveStateSocket(null);
}
}
private static List<Control> DISABLED_CONTROLS = Arrays.asList(new Control[] {
Control.CARTRIDGE_FORMAT
});
}