From d845a10de40865fc7a4aedb6c57a93d5bca88beb Mon Sep 17 00:00:00 2001 From: Brendan Robert Date: Sun, 14 Feb 2016 11:13:03 -0600 Subject: [PATCH] Issue #24: Nearly-hedless mode is confirmed and it is possible to JUnit test the actual emulator in headless mode! Right now sound doesn't respect headless mode though so it might cause issues on travis-ci. --- src/main/java/jace/JaceApplication.java | 4 +- src/main/java/jace/JaceUIController.java | 103 ++++++++++-------- src/main/java/jace/apple2e/MOS65C02.java | 19 +++- .../java/jace/assembly/AssemblyHandler.java | 10 +- src/main/java/jace/config/Configuration.java | 12 +- src/main/java/jace/core/CPU.java | 2 + src/main/java/jace/core/Utility.java | 22 +++- .../java/jace/hardware/CardAppleMouse.java | 2 +- src/main/java/jace/hardware/CardDiskII.java | 10 +- .../java/jace/hardware/CardRamFactor.java | 11 +- src/main/java/jace/hardware/CardSSC.java | 6 +- .../java/jace/hardware/CardThunderclock.java | 25 +++-- src/main/java/jace/hardware/DiskIIDrive.java | 8 +- src/main/java/jace/hardware/NoSlotClock.java | 8 +- .../hardware/massStorage/CardMassStorage.java | 4 +- .../massStorage/MassStorageDrive.java | 7 +- src/main/java/jace/ide/HeadlessProgram.java | 50 +++++++++ src/main/java/jace/ide/Program.java | 2 +- src/main/java/jace/library/DiskType.java | 3 +- src/main/java/jace/library/MediaConsumer.java | 5 +- .../jace/cpu/Basic6502FuncationalityTest.java | 90 +++++++++++++++ src/test/java/jace/ide/ApplesoftTest.java | 4 +- 22 files changed, 305 insertions(+), 102 deletions(-) create mode 100644 src/main/java/jace/ide/HeadlessProgram.java create mode 100644 src/test/java/jace/cpu/Basic6502FuncationalityTest.java diff --git a/src/main/java/jace/JaceApplication.java b/src/main/java/jace/JaceApplication.java index 576cf79..c11d048 100644 --- a/src/main/java/jace/JaceApplication.java +++ b/src/main/java/jace/JaceApplication.java @@ -47,7 +47,7 @@ public class JaceApplication extends Application { Scene s = new Scene(node); primaryStage.setScene(s); primaryStage.setTitle("Jace"); - primaryStage.getIcons().add(Utility.loadIcon("woz_figure.gif")); + Utility.loadIcon("woz_figure.gif").ifPresent(primaryStage.getIcons()::add); } catch (IOException exception) { throw new RuntimeException(exception); } @@ -86,7 +86,7 @@ public class JaceApplication extends Application { Scene s = new Scene(node); cheatStage.setScene(s); cheatStage.setTitle("Jace: MetaCheat"); - cheatStage.getIcons().add(Utility.loadIcon("woz_figure.gif")); + Utility.loadIcon("woz_figure.gif").ifPresent(cheatStage.getIcons()::add); } catch (IOException exception) { throw new RuntimeException(exception); } diff --git a/src/main/java/jace/JaceUIController.java b/src/main/java/jace/JaceUIController.java index 7181bd3..778b64c 100644 --- a/src/main/java/jace/JaceUIController.java +++ b/src/main/java/jace/JaceUIController.java @@ -3,7 +3,6 @@ * To change this template file, choose Tools | Templates * and open the template in the editor. */ - package jace; import com.sun.glass.ui.Application; @@ -57,7 +56,7 @@ public class JaceUIController { @FXML private AnchorPane rootPane; - + @FXML private StackPane stackPane; @@ -68,7 +67,7 @@ public class JaceUIController { private ImageView appleScreen; Computer computer; - + @FXML void initialize() { assert rootPane != null : "fx:id=\"rootPane\" was not injected: check your FXML file 'JaceUI.fxml'."; @@ -80,18 +79,18 @@ public class JaceUIController { rootPane.setOnDragEntered(this::processDragEnteredEvent); rootPane.setOnDragExited(this::processDragExitedEvent); } - + public void connectComputer(Computer computer, Stage primaryStage) { this.computer = computer; appleScreen.setImage(computer.getVideo().getFrameBuffer()); EventHandler keyboardHandler = computer.getKeyboard().getListener(); - primaryStage.setOnShowing(evt->computer.getKeyboard().resetState()); + primaryStage.setOnShowing(evt -> computer.getKeyboard().resetState()); rootPane.setFocusTraversable(true); rootPane.setOnKeyPressed(keyboardHandler); rootPane.setOnKeyReleased(keyboardHandler); rootPane.requestFocus(); } - + private void processDragEnteredEvent(DragEvent evt) { MediaEntry media = null; if (evt.getDragboard().hasFiles()) { @@ -115,63 +114,68 @@ public class JaceUIController { startDragEvent(media); } } - + private void processDragExitedEvent(DragEvent evt) { endDragEvent(); } - private File getDraggedFile(List files) { if (files == null || files.isEmpty()) { return null; } for (File f : files) { - if (f.exists()) return f; + if (f.exists()) { + return f; + } } return null; } HBox drivePanel; + private void startDragEvent(MediaEntry media) { List consumers = getMediaConsumers(); drivePanel = new HBox(); consumers.stream() - .filter((consumer) -> (consumer.isAccepted(media, media.files.get(0)))) - .forEach((consumer) -> { - Label icon = consumer.getIcon(); - icon.setTextFill(Color.WHITE); - icon.setPadding(new Insets(2.0)); - drivePanel.getChildren().add(icon); - icon.setOnDragOver(event -> { - event.acceptTransferModes(TransferMode.ANY); - event.consume(); - }); - icon.setOnDragDropped(event -> { - System.out.println("Dropping media on "+icon.getText()); - try { - computer.pause(); - consumer.insertMedia(media, media.files.get(0)); - computer.resume(); - event.setDropCompleted(true); - event.consume(); - } catch (IOException ex) { - Logger.getLogger(JaceUIController.class.getName()).log(Level.SEVERE, null, ex); + .filter((consumer) -> (consumer.isAccepted(media, media.files.get(0)))) + .forEach((consumer) -> { + Label icon = consumer.getIcon().orElse(null); + if (icon == null) { + return; } - endDragEvent(); + icon.setTextFill(Color.WHITE); + icon.setPadding(new Insets(2.0)); + drivePanel.getChildren().add(icon); + icon.setOnDragOver(event -> { + event.acceptTransferModes(TransferMode.ANY); + event.consume(); + }); + icon.setOnDragDropped(event -> { + System.out.println("Dropping media on " + icon.getText()); + try { + computer.pause(); + consumer.insertMedia(media, media.files.get(0)); + computer.resume(); + event.setDropCompleted(true); + event.consume(); + } catch (IOException ex) { + Logger.getLogger(JaceUIController.class.getName()).log(Level.SEVERE, null, ex); + } + endDragEvent(); + }); }); - }); - stackPane.getChildren().add(drivePanel); + stackPane.getChildren().add(drivePanel); drivePanel.setLayoutX(10); drivePanel.setLayoutY(10); } - + private void endDragEvent() { stackPane.getChildren().remove(drivePanel); drivePanel.getChildren().stream().forEach((n) -> { n.setOnDragDropped(null); }); } - + private List getMediaConsumers() { List consumers = new ArrayList<>(); for (Optional card : computer.memory.getAllCards()) { @@ -181,14 +185,16 @@ public class JaceUIController { } return consumers; } - + Map iconTTL = new ConcurrentHashMap<>(); + void addIndicator(Label icon) { addIndicator(icon, 250); } + void addIndicator(Label icon, long TTL) { if (!iconTTL.containsKey(icon)) { - Application.invokeLater(()->{ + Application.invokeLater(() -> { if (!notificationBox.getChildren().contains(icon)) { notificationBox.getChildren().add(icon); } @@ -198,39 +204,40 @@ public class JaceUIController { } void removeIndicator(Label icon) { - Application.invokeLater(()->{ + Application.invokeLater(() -> { notificationBox.getChildren().remove(icon); iconTTL.remove(icon); }); - } + } ScheduledExecutorService notificationExecutor = Executors.newSingleThreadScheduledExecutor(); ScheduledFuture ttlCleanupTask = null; + private void trackTTL(Label icon, long TTL) { - iconTTL.put(icon, System.currentTimeMillis()+TTL); - - if (ttlCleanupTask == null || ttlCleanupTask.isCancelled()) { + iconTTL.put(icon, System.currentTimeMillis() + TTL); + + if (ttlCleanupTask == null || ttlCleanupTask.isCancelled()) { ttlCleanupTask = notificationExecutor.scheduleWithFixedDelay(this::processTTL, 1, 100, TimeUnit.MILLISECONDS); } } - + private void processTTL() { Long now = System.currentTimeMillis(); iconTTL.keySet().stream() - .filter((icon) -> (iconTTL.get(icon) <= now)) - .forEach((icon) -> { - removeIndicator(icon); - }); + .filter((icon) -> (iconTTL.get(icon) <= now)) + .forEach((icon) -> { + removeIndicator(icon); + }); if (iconTTL.isEmpty()) { ttlCleanupTask.cancel(true); ttlCleanupTask = null; } } - + public void addMouseListener(EventHandler handler) { appleScreen.addEventHandler(MouseEvent.ANY, handler); } - + public void removeMouseListener(EventHandler handler) { appleScreen.removeEventHandler(MouseEvent.ANY, handler); } diff --git a/src/main/java/jace/apple2e/MOS65C02.java b/src/main/java/jace/apple2e/MOS65C02.java index b19b3cc..09574a4 100644 --- a/src/main/java/jace/apple2e/MOS65C02.java +++ b/src/main/java/jace/apple2e/MOS65C02.java @@ -77,12 +77,29 @@ public class MOS65C02 extends CPU { public MOS65C02(Computer computer) { super(computer); + clearState(); } @Override public void reconfigure() { } + @Override + public void clearState() { + A = 0x0ff; + X = 0x0ff; + Y = 0x0ff; + C = 1; + interruptSignalled = false; + Z = true; + I = true; + D = true; + B = true; + V = true; + N = true; + STACK = 0xff; + } + public enum OPCODE { ADC_IMM(0x0069, COMMAND.ADC, MODE.IMMEDIATE, 2), ADC_ZP(0x0065, COMMAND.ADC, MODE.ZEROPAGE, 3), @@ -369,7 +386,7 @@ public class MOS65C02 extends CPU { int pc = cpu.getProgramCounter(); int address = pc + 2 + cpu.getMemory().read(pc + 1, TYPE.READ_OPERAND, cpu.readAddressTriggersEvent, false); // The wait cycles are not added unless the branch actually happens! - cpu.setPageBoundaryPenalty((address & 0x00ff00) != (pc & 0x00ff00)); + cpu.setPageBoundaryPenalty((address & 0x00ff00) != ((pc+2) & 0x00ff00)); return address; }, false), IMMEDIATE(2, "#$~1", (cpu) -> cpu.getProgramCounter() + 1), diff --git a/src/main/java/jace/assembly/AssemblyHandler.java b/src/main/java/jace/assembly/AssemblyHandler.java index d462d16..5cc12b3 100644 --- a/src/main/java/jace/assembly/AssemblyHandler.java +++ b/src/main/java/jace/assembly/AssemblyHandler.java @@ -32,7 +32,11 @@ public class AssemblyHandler implements LanguageHandler { public void execute(CompileResult lastResult) { if (lastResult.isSuccessful()) { try { - Emulator.computer.pause(); + boolean resume = false; + if (Emulator.computer.isRunning()) { + resume = true; + Emulator.computer.pause(); + } RAM memory = Emulator.computer.getMemory(); FileInputStream input = new FileInputStream(lastResult.getCompiledAsset()); int startLSB = input.read(); @@ -43,7 +47,9 @@ public class AssemblyHandler implements LanguageHandler { while ((next=input.read()) != -1) { memory.write(pos++, (byte) next, false, true); } - Emulator.computer.resume(); + if (resume) { + Emulator.computer.resume(); + } } catch (IOException ex) { Logger.getLogger(AssemblyHandler.class.getName()).log(Level.SEVERE, null, ex); } diff --git a/src/main/java/jace/config/Configuration.java b/src/main/java/jace/config/Configuration.java index e60459d..277e8dc 100644 --- a/src/main/java/jace/config/Configuration.java +++ b/src/main/java/jace/config/Configuration.java @@ -51,6 +51,7 @@ import java.util.logging.Level; import java.util.logging.Logger; import javafx.collections.ObservableList; import javafx.scene.control.TreeItem; +import javafx.scene.image.Image; import javafx.scene.image.ImageView; /** @@ -93,8 +94,13 @@ public class Configuration implements Reconfigurable { return null; } - public static ImageView getChangedIcon() { - return new ImageView(Utility.loadIcon("icon_exclaim.gif")); + public static Optional getChangedIcon() { + Optional icon = Utility.loadIcon("icon_exclaim.gif"); + if (icon.isPresent()) { + return Optional.of(new ImageView(icon.get())); + } else { + return Optional.empty(); + } } @Override @@ -251,7 +257,7 @@ public class Configuration implements Reconfigurable { if (!changed) { setGraphic(null); } else { - setGraphic(getChangedIcon()); + getChangedIcon().ifPresent(this::setGraphic); } } } diff --git a/src/main/java/jace/core/CPU.java b/src/main/java/jace/core/CPU.java index aa7bb8b..3718133 100644 --- a/src/main/java/jace/core/CPU.java +++ b/src/main/java/jace/core/CPU.java @@ -175,4 +175,6 @@ public abstract class CPU extends Device { lastTrace = trace; singleTraceEnabled = false; } + + abstract public void clearState(); } \ No newline at end of file diff --git a/src/main/java/jace/core/Utility.java b/src/main/java/jace/core/Utility.java index 63f596b..533be9f 100644 --- a/src/main/java/jace/core/Utility.java +++ b/src/main/java/jace/core/Utility.java @@ -31,6 +31,7 @@ import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; @@ -280,13 +281,24 @@ public class Utility { return result; } - public static Image loadIcon(String filename) { + private static boolean isHeadless = false; + public static void setHeadlessMode(boolean headless) { + isHeadless = headless; + } + + public static Optional loadIcon(String filename) { + if (isHeadless) { + return Optional.empty(); + } InputStream stream = Utility.class.getClassLoader().getResourceAsStream("jace/data/" + filename); - return new Image(stream); + return Optional.of(new Image(stream)); } - public static Label loadIconLabel(String filename) { - Image img = loadIcon(filename); + public static Optional