// 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 }); }