This commit is contained in:
Martin Haye 2023-08-13 08:29:57 -07:00
commit bdefe148a8
24 changed files with 900 additions and 298 deletions

View File

@ -29,6 +29,14 @@
<configuration>
<source>17</source>
<target>17</target>
<annotationProcessorPaths>
<path>
<groupId>org.8bitbunch</groupId>
<artifactId>lawlesslegends</artifactId>
<version>3.0-SNAPSHOT</version>
</path>
</annotationProcessorPaths>
<annotationProcessors>jace.config.InvokableActionAnnotationProcessor</annotationProcessors>
</configuration>
<version>3.11.0</version>
</plugin>
@ -113,18 +121,6 @@
}
</moduleInfoSource>
</module>
<module>
<artifact>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.10.2</version>
</artifact>
<moduleInfoSource>
module reflections {
exports org.reflections;
}
</moduleInfoSource>
</module>
</modules>
<overwriteExistingFiles>true</overwriteExistingFiles>
</configuration>

View File

@ -72,6 +72,14 @@ public class Emulator {
return getInstance().computer;
}
public static void whileSuspended(Consumer<LawlessComputer> action) {
withComputer(c->c.getMotherboard().whileSuspended(()->action.accept(c)));
}
public static <T> T whileSuspended(Function<LawlessComputer, T> action, T defaultValue) {
return withComputer(c->c.getMotherboard().whileSuspended(()->action.apply(c), defaultValue), defaultValue);
}
public static void withComputer(Consumer<LawlessComputer> c) {
LawlessComputer computer = getComputer();
if (computer != null) {

View File

@ -209,14 +209,13 @@ public class EmulatorUILogic implements Reconfigurable {
alternatives = "Execute program;Load binary;Load program;Load rom;Play single-load game",
defaultKeyMapping = "ctrl+shift+b")
public static void runFile() {
Emulator.withComputer(c->
c.getMotherboard().whileSuspended(()->{
FileChooser select = new FileChooser();
File binary = select.showOpenDialog(LawlessLegends.getApplication().primaryStage);
if (binary != null) {
runFileNamed(binary);
}
}));
Emulator.whileSuspended(c-> {
FileChooser select = new FileChooser();
File binary = select.showOpenDialog(LawlessLegends.getApplication().primaryStage);
if (binary != null) {
runFileNamed(binary);
}
});
}
public static void runFileNamed(File binary) {
@ -244,13 +243,13 @@ public class EmulatorUILogic implements Reconfigurable {
in.read(data);
}
Emulator.withComputer(c->c.getMotherboard().whileSuspended(()->{
Emulator.whileSuspended(c-> {
RAM ram = c.getMemory();
for (int i = 0; i < data.length; i++) {
ram.write(address + i, data[i], false, true);
}
c.getCpu().setProgramCounter(address);
}));
});
}
@InvokableAction(
@ -311,22 +310,20 @@ public class EmulatorUILogic implements Reconfigurable {
}
File outFile = new File("screen_" + type + "_a" + Integer.toHexString(start) + "_" + timestamp);
try (FileOutputStream out = new FileOutputStream(outFile)) {
Emulator.withComputer(c -> {
Emulator.whileSuspended(c -> {
RAM128k ram = (RAM128k) c.getMemory();
c.getMotherboard().whileSuspended(() -> {
try {
if (dres) {
for (int i = 0; i < len; i++) {
out.write(ram.getAuxVideoMemory().readByte(start + i));
}
}
try {
if (dres) {
for (int i = 0; i < len; i++) {
out.write(ram.getMainMemory().readByte(start + i));
out.write(ram.getAuxVideoMemory().readByte(start + i));
}
} catch (IOException e) {
Logger.getLogger(EmulatorUILogic.class.getName()).log(Level.SEVERE, "Error writing screenshot", e);
}
});
for (int i = 0; i < len; i++) {
out.write(ram.getMainMemory().readByte(start + i));
}
} catch (IOException e) {
Logger.getLogger(EmulatorUILogic.class.getName()).log(Level.SEVERE, "Error writing screenshot", e);
}
});
}
System.out.println("Wrote screenshot to " + outFile.getAbsolutePath());

View File

@ -13,6 +13,7 @@ import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.logging.Level;
import java.util.logging.Logger;
@ -259,8 +260,8 @@ public class JaceUIController {
private void connectButtons(Node n) {
if (n instanceof Button button) {
Runnable action = Utility.getNamedInvokableAction(button.getText());
button.setOnMouseClicked(evt -> action.run());
Function<Boolean, Boolean> action = Utility.getNamedInvokableAction(button.getText());
button.setOnMouseClicked(evt -> action.apply(false));
} else if (n instanceof Parent parent) {
parent.getChildrenUnmodifiable().forEach(child -> connectButtons(child));
}
@ -378,17 +379,15 @@ public class JaceUIController {
});
icon.setOnDragDropped(event -> {
System.out.println("Dropping media on " + icon.getText());
Emulator.withComputer(c -> {
c.getMotherboard().whileSuspended(() -> {
try {
consumer.insertMedia(media, media.files.get(0));
} catch (IOException ex) {
Logger.getLogger(JaceUIController.class.getName()).log(Level.SEVERE, null, ex);
}
});
});
event.setDropCompleted(true);
event.consume();
Emulator.whileSuspended(c-> {
try {
consumer.insertMedia(media, media.files.get(0));
} catch (IOException ex) {
Logger.getLogger(JaceUIController.class.getName()).log(Level.SEVERE, null, ex);
}
});
event.setDropCompleted(true);
event.consume();
endDragEvent();
});
});

View File

@ -426,7 +426,7 @@ public class VideoDHGR extends Video {
protected void displayDoubleHires(WritableImage screen, int xOffset, int y, int rowAddress) {
// Skip odd columns since this does two at once
if ((xOffset & 0x01) == 1) {
if ((xOffset & 0x01) == 1 || xOffset < 0) {
return;
}
int b1 = ((RAM128k) computer.getMemory()).getAuxVideoMemory().readByte(rowAddress + xOffset );
@ -452,7 +452,7 @@ public class VideoDHGR extends Video {
protected void displayHires(WritableImage screen, int xOffset, int y, int rowAddress) {
// Skip odd columns since this does two at once
if ((xOffset & 0x01) == 1) {
if ((xOffset & 0x01) == 1 || xOffset < 0) {
return;
}
int b1 = 0x0ff & ((RAM128k) computer.getMemory()).getMainMemory().readByte(rowAddress + xOffset);
@ -465,6 +465,7 @@ public class VideoDHGR extends Video {
}
protected void displayLores(WritableImage screen, int xOffset, int y, int rowAddress) {
if (xOffset < 0) return;
int c1 = ((RAM128k) computer.getMemory()).getMainMemory().readByte(rowAddress + xOffset) & 0x0FF;
if ((y & 7) < 4) {
c1 &= 15;
@ -492,6 +493,7 @@ public class VideoDHGR extends Video {
}
protected void displayDoubleLores(WritableImage screen, int xOffset, int y, int rowAddress) {
if (xOffset < 0) return;
int c1 = ((RAM128k) computer.getMemory()).getAuxVideoMemory().readByte(rowAddress + xOffset) & 0x0FF;
int c2 = ((RAM128k) computer.getMemory()).getMainMemory().readByte(rowAddress + xOffset) & 0x0FF;
if ((y & 7) < 4) {
@ -554,7 +556,7 @@ public class VideoDHGR extends Video {
protected void displayText(WritableImage screen, int xOffset, int y, int rowAddress) {
// Skip odd columns since this does two at once
if ((xOffset & 0x01) == 1) {
if ((xOffset & 0x01) == 1 || xOffset < 0) {
return;
}
int yOffset = y & 7;
@ -571,7 +573,7 @@ public class VideoDHGR extends Video {
protected void displayText80(WritableImage screen, int xOffset, int y, int rowAddress) {
// Skip odd columns since this does two at once
if ((xOffset & 0x01) == 1) {
if ((xOffset & 0x01) == 1 || xOffset < 0) {
return;
}
int yOffset = y & 7;

View File

@ -143,6 +143,7 @@ public class VideoNTSC extends VideoDHGR {
@Override
protected void showBW(WritableImage screen, int x, int y, int dhgrWord) {
if (x < 0) return;
int pos = divBy28[x];
if (rowStart < 0) {
rowStart = pos;
@ -153,6 +154,7 @@ public class VideoNTSC extends VideoDHGR {
@Override
protected void showDhgr(WritableImage screen, int x, int y, int dhgrWord) {
if (x < 0) return;
int pos = divBy28[x];
if (rowStart < 0) {
rowStart = pos;
@ -163,6 +165,7 @@ public class VideoNTSC extends VideoDHGR {
@Override
protected void displayLores(WritableImage screen, int xOffset, int y, int rowAddress) {
if (xOffset < 0) return;
int data = ((RAM128k) computer.getMemory()).getMainMemory().readByte(rowAddress + xOffset) & 0x0FF;
int pos = xOffset >> 1;
if (rowStart < 0) {
@ -193,6 +196,7 @@ public class VideoNTSC extends VideoDHGR {
@Override
protected void displayDoubleLores(WritableImage screen, int xOffset, int y, int rowAddress) {
if (xOffset < 0) return;
int pos = xOffset >> 1;
if (rowStart < 0) {
rowStart = pos;
@ -257,6 +261,7 @@ public class VideoNTSC extends VideoDHGR {
// Reset scanline position
int byteCounter = 0;
for (int s = rowStart; s < 20; s++) {
if (s < 0) continue;
int add = 0;
int bits;
if (hiresMode) {

View File

@ -150,7 +150,7 @@ public class ApplesoftProgram {
}
public void run() {
Emulator.withComputer(c->c.getMotherboard().whileSuspended(()->{
Emulator.whileSuspended(c-> {
int programStart = c.getMemory().readWordRaw(START_OF_PROG_POINTER);
int programEnd = programStart + getProgramSize();
if (isProgramRunning()) {
@ -162,7 +162,7 @@ public class ApplesoftProgram {
injectProgram();
clearVariables(programEnd);
}
}));
});
}
private void injectProgram() {

View File

@ -30,7 +30,6 @@ import java.io.ObjectStreamException;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
@ -43,13 +42,13 @@ import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Stream;
import jace.Emulator;
import jace.EmulatorUILogic;
import jace.core.Computer;
import jace.core.Keyboard;
import jace.core.Utility;
import javafx.collections.FXCollections;
@ -64,15 +63,6 @@ import javafx.scene.image.ImageView;
*/
public class Configuration implements Reconfigurable {
private static Method findAnyMethodByName(Class<? extends Reconfigurable> aClass, String m) {
for (Method method : aClass.getMethods()) {
if (method.getName().equals(m)) {
return method;
}
}
return null;
}
static ConfigurableField getConfigurableFieldInfo(Reconfigurable subject, String settingName) {
Field f;
try {
@ -88,15 +78,6 @@ public class Configuration implements Reconfigurable {
return (f != null && !f.shortName().equals("")) ? f.shortName() : longName;
}
public static InvokableAction getInvokableActionInfo(Reconfigurable subject, String actionName) {
for (Method m : subject.getClass().getMethods()) {
if (m.getName().equals(actionName) && m.isAnnotationPresent(InvokableAction.class)) {
return m.getAnnotation(InvokableAction.class);
}
}
return null;
}
public static Optional<ImageView> getChangedIcon() {
return Utility.loadIcon("icon_exclaim.gif").map(ImageView::new);
}
@ -301,13 +282,13 @@ public class Configuration implements Reconfigurable {
return;
}
for (Method m : node.subject.getClass().getMethods()) {
if (!m.isAnnotationPresent(InvokableAction.class)) {
continue;
}
InvokableAction action = m.getDeclaredAnnotation(InvokableAction.class);
node.hotkeys.put(m.getName(), action.defaultKeyMapping());
}
InvokableActionRegistry registry = InvokableActionRegistry.getInstance();
registry.getStaticMethodNames(node.subject.getClass()).stream().forEach((name) ->
node.hotkeys.put(name, registry.getStaticMethodInfo(name).defaultKeyMapping())
);
registry.getInstanceMethodNames(node.subject.getClass()).stream().forEach((name) ->
node.hotkeys.put(name, registry.getInstanceMethodInfo(name).defaultKeyMapping())
);
for (Field f : node.subject.getClass().getFields()) {
// System.out.println("Evaluating field " + f.getName());
@ -337,6 +318,8 @@ public class Configuration implements Reconfigurable {
if (child == null || !child.subject.equals(o)) {
child = new ConfigNode(node, r);
node.putChild(f.getName(), child);
} else {
Logger.getLogger(Configuration.class.getName()).severe("Unable to find child named %s for node %s".formatted(r.getName(), node.name));
}
buildTree(child, visited);
} else if (o.getClass().isArray()) {
@ -470,6 +453,34 @@ public class Configuration implements Reconfigurable {
return new File(System.getProperty("user.dir"), ".jace.conf");
}
public static void registerKeyHandlers() {
registerKeyHandlers(BASE, true);
}
public static void registerKeyHandlers(ConfigNode node, boolean recursive) {
Keyboard.unregisterAllHandlers(node.subject);
InvokableActionRegistry registry = InvokableActionRegistry.getInstance();
node.hotkeys.keySet().stream().forEach((name) -> {
InvokableAction action = registry.getStaticMethodInfo(name);
if (action != null) {
for (String code : node.hotkeys.get(name)) {
Keyboard.registerInvokableAction(action, node.subject, registry.getStaticFunction(name), code);
}
}
action = registry.getInstanceMethodInfo(name);
if (action != null) {
for (String code : node.hotkeys.get(name)) {
Keyboard.registerInvokableAction(action, node.subject, registry.getInstanceFunction(name), code);
}
}
});
if (recursive) {
node.getChildren().stream().forEach((child) -> {
registerKeyHandlers(child, true);
});
}
}
/**
* Apply settings from node tree to the object model This also calls
* "reconfigure" on objects in sequence
@ -479,31 +490,26 @@ public class Configuration implements Reconfigurable {
* descendants
*/
public static boolean applySettings(ConfigNode node) {
boolean resume = false;
if (node == BASE) {
resume = Emulator.withComputer(c->c.pause(), false);
}
boolean hasChanged = false;
if (node.changed) {
doApply(node);
hasChanged = true;
}
AtomicBoolean hasChanged = new AtomicBoolean(false);
// Now that the object structure reflects the current configuration,
// process reconfiguration from the children, etc.
for (ConfigNode child : node.getChildren()) {
hasChanged |= applySettings(child);
}
Emulator.whileSuspended(c-> {
if (node.changed) {
doApply(node);
hasChanged.set(true);
}
if (node.equals(BASE) && hasChanged) {
// Now that the object structure reflects the current configuration,
// process reconfiguration from the children, etc.
for (ConfigNode child : node.getChildren()) {
if (applySettings(child)) hasChanged.set(true);
}
});
if (node.equals(BASE) && hasChanged.get()) {
buildTree();
}
if (resume) {
Emulator.withComputer(Computer::resume);
}
return hasChanged;
return hasChanged.get();
}
private static void applyConfigTree(ConfigNode newRoot, ConfigNode oldRoot) {
@ -529,16 +535,7 @@ public class Configuration implements Reconfigurable {
private static void doApply(ConfigNode node) {
List<String> removeList = new ArrayList<>();
Keyboard.unregisterAllHandlers(node.subject);
node.hotkeys.keySet().stream().forEach((m) -> {
Method method = findAnyMethodByName(node.subject.getClass(), m);
if (method != null) {
InvokableAction action = method.getAnnotation(InvokableAction.class);
for (String code : node.hotkeys.get(m)) {
Keyboard.registerInvokableAction(action, node.subject, method, code);
}
}
});
registerKeyHandlers(node, false);
for (String f : node.settings.keySet()) {
try {

View File

@ -1,30 +1,38 @@
package jace.config;
import jace.config.Configuration.ConfigNode;
import javafx.beans.Observable;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import javafx.util.StringConverter;
import java.io.File;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import jace.config.Configuration.ConfigNode;
import javafx.beans.Observable;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.SplitPane;
import javafx.scene.control.TextField;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import javafx.util.StringConverter;
public class ConfigurationUIController {
public static final String DELIMITER = "~!~";
@ -155,7 +163,7 @@ public class ConfigurationUIController {
if (node == null) {
return;
}
node.hotkeys.forEach((name, values) -> settingsVbox.getChildren().add(buildKeyShortcutRow(node, name, values)));
node.hotkeys.forEach((name, values) -> buildKeyShortcutRow(node, name, values).ifPresent(settingsVbox.getChildren()::add));
node.settings.forEach((name, value) -> settingsVbox.getChildren().add(buildSettingRow(node, name, value)));
}
@ -176,10 +184,14 @@ public class ConfigurationUIController {
return row;
}
private Node buildKeyShortcutRow(ConfigNode node, String actionName, String[] values) {
InvokableAction actionInfo = Configuration.getInvokableActionInfo(node.subject, actionName);
private Optional<Node> buildKeyShortcutRow(ConfigNode node, String actionName, String[] values) {
InvokableActionRegistry registry = InvokableActionRegistry.getInstance();
InvokableAction actionInfo = registry.getInstanceMethodInfo(actionName);
if (actionInfo == null) {
return null;
actionInfo = registry.getStaticMethodInfo(actionName);
}
if (actionInfo == null) {
return Optional.empty();
}
HBox row = new HBox();
row.getStyleClass().add("setting-row");
@ -194,7 +206,7 @@ public class ConfigurationUIController {
label.setLabelFor(widget);
row.getChildren().add(label);
row.getChildren().add(widget);
return row;
return Optional.of(row);
}
private void editKeyboardShortcut(ConfigNode node, String actionName, Text widget) {

View File

@ -0,0 +1,168 @@
package jace.config;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
// Compile-time annotation processor which creates a registry of all static methods annotated with @InvokableAction.
@SupportedSourceVersion(SourceVersion.RELEASE_17)
@SupportedAnnotationTypes("jace.config.InvokableAction")
public class InvokableActionAnnotationProcessor extends AbstractProcessor {
Messager messager;
Map<InvokableAction, ExecutableElement> staticMethods = new HashMap<>();
Map<InvokableAction, ExecutableElement> instanceMethods = new HashMap<>();
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
this.messager = processingEnv.getMessager();
messager.printMessage(javax.tools.Diagnostic.Kind.NOTE, "InvokableActionAnnotationProcessor init()");
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
messager.printMessage(javax.tools.Diagnostic.Kind.NOTE, "InvokableActionAnnotationProcessor process()");
// Get list of methods annotated with @InvokableAction.
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(InvokableAction.class);
for (Element element : elements) {
if (element.getModifiers().contains(javax.lang.model.element.Modifier.STATIC)) {
// If the annotation method is static, add it to the static method registry.
trackStaticMethod(element);
} else {
// For non-static methods, track in a separate registry.
trackInstanceMethod(element);
}
try {
// Write class that contains static and instance methods.
writeRegistryClass();
} catch (IOException ex) {
messager.printMessage(javax.tools.Diagnostic.Kind.ERROR, "Error writing InvokableActionRegistry.java: " + ex.getMessage());
}
}
return true;
}
private void trackStaticMethod(Element element) {
// Store the method in the static method registry.
staticMethods.put(element.getAnnotation(InvokableAction.class), (ExecutableElement) element);
}
private void trackInstanceMethod(Element element) {
// Store the method in the instance method registry.
instanceMethods.put(element.getAnnotation(InvokableAction.class), (ExecutableElement) element);
}
private String serializeArrayOfStrings(String... strings) {
return Arrays.stream(strings).map(s -> "\"" + s + "\"").collect(Collectors.joining(","));
}
private void serializeInvokableAction(InvokableAction annotation, String variableName, PrintWriter writer) {
writer.append("""
%s = createInvokableAction("%s", "%s", "%s", "%s", %s, %s, new String[] {%s});
""".formatted(
variableName,
annotation.name(),
annotation.category(),
annotation.description(),
annotation.alternatives(),
annotation.consumeKeyEvent(),
annotation.notifyOnRelease(),
serializeArrayOfStrings(annotation.defaultKeyMapping())
));
}
// Write the registry class.
private void writeRegistryClass() throws IOException {
Files.createDirectories(new File("target/generated-sources/jace/config").toPath());
try (PrintWriter writer = new PrintWriter(new FileWriter("target/generated-sources/jace/config/InvokableActionRegistryImpl.java"))) {
writer.write("""
package jace.config;
import java.util.logging.Level;
public class InvokableActionRegistryImpl extends InvokableActionRegistry {
@Override
public void init() {
InvokableAction annotation;
""");
for (Map.Entry<InvokableAction, ExecutableElement> entry : staticMethods.entrySet()) {
InvokableAction annotation = entry.getKey();
ExecutableElement method = entry.getValue();
String packageName = method.getEnclosingElement().getEnclosingElement().toString();
String className = method.getEnclosingElement().getSimpleName().toString();
String fqnClassName = packageName + "." + className;
serializeInvokableAction(annotation, "annotation", writer);
boolean takesBoolenParameter = method.getParameters().size() == 1 && method.getParameters().get(0).asType().toString().equalsIgnoreCase("boolean");
boolean returnsBoolean = method.getReturnType().toString().equalsIgnoreCase("boolean");
writer.write("""
putStaticAction(annotation.name(), %s.class, annotation, (b) -> {
try {
%s %s.%s(%s);
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error invoking %s", ex);
%s
}
});
""".formatted(
fqnClassName,
returnsBoolean ? "return " : "",
fqnClassName,
method.getSimpleName(),
takesBoolenParameter ? "b" : "",
fqnClassName + "." + method.getSimpleName(),
returnsBoolean ? "return false;" : ""
));
}
// Now for the instance methods, do the same except use a biconsumer which takes the instance as well as the boolean parameter.
for (Map.Entry<InvokableAction, ExecutableElement> entry : instanceMethods.entrySet()) {
InvokableAction annotation = entry.getKey();
ExecutableElement method = entry.getValue();
String packageName = method.getEnclosingElement().getEnclosingElement().toString();
String className = method.getEnclosingElement().getSimpleName().toString();
String fqnClassName = packageName + "." + className;
serializeInvokableAction(annotation, "annotation", writer);
boolean takesBoolenParameter = method.getParameters().size() == 1 && method.getParameters().get(0).asType().toString().equalsIgnoreCase("boolean");
boolean returnsBoolean = method.getReturnType().toString().equalsIgnoreCase("boolean");
writer.write("""
putInstanceAction(annotation.name(), %s.class, annotation, (o, b) -> {
try {
%s ((%s) o).%s(%s);
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error invoking %s", ex);
%s
}
});
""".formatted(
fqnClassName,
returnsBoolean ? "return " : "",
fqnClassName,
method.getSimpleName(),
takesBoolenParameter ? "b" : "",
fqnClassName + "." + method.getSimpleName(),
returnsBoolean ? "return false;" : ""
));
}
writer.write("}\n}");
}
}
}

View File

@ -0,0 +1,148 @@
package jace.config;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.logging.Logger;
public abstract class InvokableActionRegistry {
protected static final Logger logger = Logger.getLogger(InvokableActionRegistry.class.getName());
private final Map<Class, Set<String>> staticMethodNames = new HashMap<>();
private final Map<String, InvokableAction> staticMethodInfo = new HashMap<>();
private final Map<String, Function<Boolean, Boolean>> staticMethodCallers = new HashMap<>();
private final Map<Class, Set<String>> instanceMethodNames = new HashMap<>();
private final Map<String, InvokableAction> instanceMethodInfo = new HashMap<>();
private final Map<String, BiFunction<Object, Boolean, Boolean>> instanceMethodCallers = new HashMap<>();
protected static InvokableActionRegistry instance;
public static InvokableActionRegistry getInstance() {
if (instance == null) {
instance = new InvokableActionRegistryImpl();
instance.init();
}
return instance;
}
abstract public void init();
final public void putStaticAction(String name, Class c, InvokableAction action, Consumer<Boolean> caller) {
putStaticAction(name, c, action, (b) -> {
caller.accept(b);
return false;
});
}
final public void putStaticAction(String name, Class c, InvokableAction action, Function<Boolean, Boolean> caller) {
staticMethodInfo.put(name, action);
staticMethodCallers.put(name, caller);
staticMethodNames.computeIfAbsent(c, k -> new TreeSet<>()).add(name);
}
public void putInstanceAction(String name, Class c, InvokableAction action, BiConsumer<Object, Boolean> caller) {
putInstanceAction(name, c, action, (o, b) -> {
caller.accept(o, b);
return false;
});
}
public void putInstanceAction(String name, Class c, InvokableAction action, BiFunction<Object, Boolean, Boolean> caller) {
instanceMethodInfo.put(name, action);
instanceMethodCallers.put(name, caller);
instanceMethodNames.computeIfAbsent(c, k -> new TreeSet<>()).add(name);
}
public Set<String> getStaticMethodNames(Class c) {
// Build a set of all the method names for this class and all its superclasses.
Set<String> result = new TreeSet<>();
Class current = c;
while (current != null) {
result.addAll(staticMethodNames.getOrDefault(current, Collections.EMPTY_SET));
current = current.getSuperclass();
}
return result;
}
public Set<String> getInstanceMethodNames(Class c) {
// Build a set of all the method names for this class and all its superclasses.
Set<String> result = new TreeSet<>();
Class current = c;
while (current != null) {
result.addAll(instanceMethodNames.getOrDefault(current, Collections.EMPTY_SET));
current = current.getSuperclass();
}
return result;
}
public InvokableAction getStaticMethodInfo(String name) {
return staticMethodInfo.get(name);
}
public InvokableAction getInstanceMethodInfo(String name) {
return instanceMethodInfo.get(name);
}
public Function<Boolean, Boolean> getStaticFunction(String name) {
return staticMethodCallers.get(name);
}
public BiFunction<Object, Boolean, Boolean> getInstanceFunction(String name) {
return instanceMethodCallers.get(name);
}
public Set<InvokableAction> getAllStaticActions() {
return new HashSet<>(staticMethodInfo.values());
}
protected InvokableAction createInvokableAction(String name, String category, String description, String alternatives, boolean consumeKeyEvent, boolean notifyOnRelease, String[] defaultKeyMapping) {
return new InvokableAction() {
@Override
public String name() {
return name;
}
@Override
public String category() {
return category;
}
@Override
public String description() {
return description;
}
@Override
public String alternatives() {
return alternatives;
}
@Override
public boolean consumeKeyEvent() {
return consumeKeyEvent;
}
@Override
public boolean notifyOnRelease() {
return notifyOnRelease;
}
@Override
public String[] defaultKeyMapping() {
return defaultKeyMapping;
}
@Override
public Class<? extends java.lang.annotation.Annotation> annotationType() {
return InvokableAction.class;
}
};
}
}

View File

@ -0,0 +1,233 @@
package jace.config;
import java.util.logging.Level;
// NOTE: This is generated code. Do not edit.
public class InvokableActionRegistryImpl extends InvokableActionRegistry {
@Override
public void init() {
InvokableAction annotation;
annotation = createInvokableAction("Resize window", "general", "Resize the screen to 1x/1.5x/2x/3x video size", "Aspect;Adjust screen;Adjust window size;Adjust aspect ratio;Fix screen;Fix window size;Fix aspect ratio;Correct aspect ratio;", true, false, new String[]{"ctrl+shift+a"});
putStaticAction(annotation.name(), jace.EmulatorUILogic.class, annotation, (b) -> {
try {
jace.EmulatorUILogic.scaleIntegerRatio();
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error invoking jace.EmulatorUILogic.scaleIntegerRatio", ex);
}
});
annotation = createInvokableAction("Rewind", "General", "Go back 1 second", "Timewarp", true, false, new String[]{"ctrl+shift+Open Bracket"});
putStaticAction(annotation.name(), jace.state.StateManager.class, annotation, (b) -> {
try {
jace.state.StateManager.beKindRewind();
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error invoking jace.state.StateManager.beKindRewind", ex);
}
});
annotation = createInvokableAction("Configuration", "general", "Edit emulator configuraion", "Reconfigure;Preferences;Settings;Config", true, false, new String[]{"f4", "ctrl+shift+c"});
putStaticAction(annotation.name(), jace.EmulatorUILogic.class, annotation, (b) -> {
try {
jace.EmulatorUILogic.showConfig();
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error invoking jace.EmulatorUILogic.showConfig", ex);
}
});
annotation = createInvokableAction("Load settings", "general", "Load all configuration settings previously saved", "load preferences;revert settings;revert preferences", true, false, new String[]{"meta+ctrl+r"});
putStaticAction(annotation.name(), jace.config.Configuration.class, annotation, (b) -> {
try {
jace.config.Configuration.loadSettings();
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error invoking jace.config.Configuration.loadSettings", ex);
}
});
annotation = createInvokableAction("About", "general", "Display about window", "info;credits", true, false, new String[]{"ctrl+shift+."});
putStaticAction(annotation.name(), jace.EmulatorUILogic.class, annotation, (b) -> {
try {
jace.EmulatorUILogic.showAboutWindow();
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error invoking jace.EmulatorUILogic.showAboutWindow", ex);
}
});
annotation = createInvokableAction("Record sound", "sound", "Toggles recording (saving) sound output to a file", "", true, false, new String[]{"ctrl+shift+w"});
putStaticAction(annotation.name(), jace.apple2e.Speaker.class, annotation, (b) -> {
try {
jace.apple2e.Speaker.toggleFileOutput();
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error invoking jace.apple2e.Speaker.toggleFileOutput", ex);
}
});
annotation = createInvokableAction("BRUN file", "file", "Loads a binary file in memory and executes it. File should end with #06xxxx, where xxxx is the start address in hex", "Execute program;Load binary;Load program;Load rom;Play single-load game", true, false, new String[]{"ctrl+shift+b"});
putStaticAction(annotation.name(), jace.EmulatorUILogic.class, annotation, (b) -> {
try {
jace.EmulatorUILogic.runFile();
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error invoking jace.EmulatorUILogic.runFile", ex);
}
});
annotation = createInvokableAction("Save Raw Screenshot", "general", "Save raw (RAM) format of visible screen", "screendump;raw screenshot", true, false, new String[]{"ctrl+shift+z"});
putStaticAction(annotation.name(), jace.EmulatorUILogic.class, annotation, (b) -> {
try {
jace.EmulatorUILogic.saveScreenshotRaw();
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error invoking jace.EmulatorUILogic.saveScreenshotRaw", ex);
}
});
annotation = createInvokableAction("Save settings", "general", "Save all configuration settings as defaults", "save preferences;save defaults", true, false, new String[]{"meta+ctrl+s"});
putStaticAction(annotation.name(), jace.config.Configuration.class, annotation, (b) -> {
try {
jace.config.Configuration.saveSettings();
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error invoking jace.config.Configuration.saveSettings", ex);
}
});
annotation = createInvokableAction("Toggle fullscreen", "general", "Activate/deactivate fullscreen mode", "fullscreen;maximize", true, false, new String[]{"ctrl+shift+f"});
putStaticAction(annotation.name(), jace.EmulatorUILogic.class, annotation, (b) -> {
try {
jace.EmulatorUILogic.toggleFullscreen();
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error invoking jace.EmulatorUILogic.toggleFullscreen", ex);
}
});
annotation = createInvokableAction("Toggle Debug", "debug", "Show/hide the debug panel", "Show Debug;Hide Debug;Inspect", true, false, new String[]{"ctrl+shift+d"});
putStaticAction(annotation.name(), jace.EmulatorUILogic.class, annotation, (b) -> {
try {
jace.EmulatorUILogic.toggleDebugPanel();
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error invoking jace.EmulatorUILogic.toggleDebugPanel", ex);
}
});
annotation = createInvokableAction("Refresh screen", "display", "Marks screen contents as changed, forcing full screen redraw", "redraw", true, false, new String[]{"ctrl+shift+r"});
putStaticAction(annotation.name(), jace.core.Video.class, annotation, (b) -> {
try {
jace.core.Video.forceRefresh();
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error invoking jace.core.Video.forceRefresh", ex);
}
});
annotation = createInvokableAction("Toggle video mode", "video", "", "Gfx mode;color;b&w;monochrome", true, false, new String[]{"ctrl+shift+g"});
putStaticAction(annotation.name(), jace.apple2e.VideoNTSC.class, annotation, (b) -> {
try {
jace.apple2e.VideoNTSC.changeVideoMode();
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error invoking jace.apple2e.VideoNTSC.changeVideoMode", ex);
}
});
annotation = createInvokableAction("Save Screenshot", "general", "Save image of visible screen", "Save image;save framebuffer;screenshot", true, false, new String[]{"ctrl+shift+s"});
putStaticAction(annotation.name(), jace.EmulatorUILogic.class, annotation, (b) -> {
try {
jace.EmulatorUILogic.saveScreenshot();
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error invoking jace.EmulatorUILogic.saveScreenshot", ex);
}
});
annotation = createInvokableAction("Paste clipboard", "Keyboard", "", "paste", true, false, new String[]{"Ctrl+Shift+V", "Shift+Insert"});
putStaticAction(annotation.name(), jace.core.Keyboard.class, annotation, (b) -> {
try {
jace.core.Keyboard.pasteFromClipboard();
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error invoking jace.core.Keyboard.pasteFromClipboard", ex);
}
});
annotation = createInvokableAction("Open IDE", "development", "Open new IDE window for Basic/Assembly/Plasma coding", "IDE;dev;development;acme;assembler;editor", true, false, new String[]{"ctrl+shift+i"});
putStaticAction(annotation.name(), jace.EmulatorUILogic.class, annotation, (b) -> {
try {
jace.EmulatorUILogic.showIDE();
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error invoking jace.EmulatorUILogic.showIDE", ex);
}
});
annotation = createInvokableAction("Up", "joystick", "", "", true, true, new String[]{"up"});
putInstanceAction(annotation.name(), jace.hardware.Joystick.class, annotation, (o, b) -> {
try {
return ((jace.hardware.Joystick) o).joystickUp(b);
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error invoking jace.hardware.Joystick.joystickUp", ex);
return false;
}
});
annotation = createInvokableAction("Open Apple Key", "Keyboard", "", "OA", false, true, new String[]{"Alt"});
putInstanceAction(annotation.name(), jace.core.Keyboard.class, annotation, (o, b) -> {
try {
((jace.core.Keyboard) o).openApple(b);
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error invoking jace.core.Keyboard.openApple", ex);
}
});
annotation = createInvokableAction("Left", "joystick", "", "", true, true, new String[]{"left"});
putInstanceAction(annotation.name(), jace.hardware.Joystick.class, annotation, (o, b) -> {
try {
return ((jace.hardware.Joystick) o).joystickLeft(b);
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error invoking jace.hardware.Joystick.joystickLeft", ex);
return false;
}
});
annotation = createInvokableAction("Right", "joystick", "", "", true, true, new String[]{"right"});
putInstanceAction(annotation.name(), jace.hardware.Joystick.class, annotation, (o, b) -> {
try {
return ((jace.hardware.Joystick) o).joystickRight(b);
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error invoking jace.hardware.Joystick.joystickRight", ex);
return false;
}
});
annotation = createInvokableAction("Closed Apple Key", "Keyboard", "", "CA", false, true, new String[]{"Shortcut", "Meta", "Command"});
putInstanceAction(annotation.name(), jace.core.Keyboard.class, annotation, (o, b) -> {
try {
((jace.core.Keyboard) o).solidApple(b);
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error invoking jace.core.Keyboard.solidApple", ex);
}
});
annotation = createInvokableAction("Down", "joystick", "", "", true, true, new String[]{"down"});
putInstanceAction(annotation.name(), jace.hardware.Joystick.class, annotation, (o, b) -> {
try {
return ((jace.hardware.Joystick) o).joystickDown(b);
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error invoking jace.hardware.Joystick.joystickDown", ex);
return false;
}
});
annotation = createInvokableAction("Pause", "General", "Stops the computer, allowing reconfiguration of core elements", "freeze;halt", true, false, new String[]{"meta+pause", "alt+pause"});
putInstanceAction(annotation.name(), jace.core.Computer.class, annotation, (o, b) -> {
try {
return ((jace.core.Computer) o).pause();
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error invoking jace.core.Computer.pause", ex);
return false;
}
});
annotation = createInvokableAction("Cold boot", "general", "Process startup sequence from power-up", "Full reset;reset emulator", true, false, new String[]{"Ctrl+Shift+Backspace", "Ctrl+Shift+Delete"});
putInstanceAction(annotation.name(), jace.core.Computer.class, annotation, (o, b) -> {
try {
((jace.core.Computer) o).invokeColdStart();
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error invoking jace.core.Computer.invokeColdStart", ex);
}
});
annotation = createInvokableAction("Warm boot", "general", "Process user-initatiated reboot (ctrl+apple+reset)", "reboot;reset;three-finger-salute;restart", true, false, new String[]{"Ctrl+Ignore Alt+Ignore Meta+Backspace", "Ctrl+Ignore Alt+Ignore Meta+Delete"});
putInstanceAction(annotation.name(), jace.core.Computer.class, annotation, (o, b) -> {
try {
((jace.core.Computer) o).invokeWarmStart();
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error invoking jace.core.Computer.invokeWarmStart", ex);
}
});
annotation = createInvokableAction("Toggle Cheats", "General", "", "cheat;Plug-in", true, false, new String[]{"ctrl+shift+m"});
putInstanceAction(annotation.name(), jace.cheat.Cheats.class, annotation, (o, b) -> {
try {
((jace.cheat.Cheats) o).toggleCheats();
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error invoking jace.cheat.Cheats.toggleCheats", ex);
}
});
annotation = createInvokableAction("Resume", "General", "Resumes the computer if it was previously paused", "unpause;unfreeze;resume;play", true, false, new String[]{"meta+shift+pause", "alt+shift+pause"});
putInstanceAction(annotation.name(), jace.core.Computer.class, annotation, (o, b) -> {
try {
((jace.core.Computer) o).resume();
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error invoking jace.core.Computer.resume", ex);
}
});
}
}

View File

@ -18,6 +18,7 @@
*/
package jace.core;
import jace.Emulator;
import jace.apple2e.SoftSwitches;
/**
@ -102,7 +103,7 @@ public abstract class Card extends Device {
@Override
public void reconfigure() {
//super.reconfigure();
computer.motherboard.whileSuspended(() -> {
Emulator.whileSuspended(c-> {
unregisterListeners();
registerListeners();
});

View File

@ -45,9 +45,9 @@ import javafx.beans.value.ChangeListener;
*/
public abstract class Computer implements Reconfigurable {
private RAM memory;
public RAM memory;
public CPU cpu;
private Video video;
public Video video;
public Keyboard keyboard;
public StateManager stateManager;
public Motherboard motherboard;

View File

@ -154,7 +154,7 @@ public abstract class Device implements Reconfigurable {
return run;
}
public boolean isPaused() {
public final boolean isPaused() {
return paused;
}
@ -163,7 +163,7 @@ public abstract class Device implements Reconfigurable {
updateTickHandler();
}
public final synchronized void setPaused(boolean paused) {
public synchronized void setPaused(boolean paused) {
this.paused = paused;
updateTickHandler();
}
@ -187,22 +187,43 @@ public abstract class Device implements Reconfigurable {
}
}
public void whilePaused(Runnable r) {
if (isRunning() && !isPaused()) {
setPaused(true);
r.run();
setPaused(false);
} else {
r.run();
}
}
public <T> T whileSuspended(Supplier<T> r, T defaultValue) {
T result;
if (isRunning()) {
suspend();
T result = r.get();
result = r.get();
resume();
return result;
} else {
return defaultValue;
result = r.get();
}
return result != null ? result : defaultValue;
}
public <T> T whilePaused(Supplier<T> r, T defaultValue) {
T result;
if (!isPaused() && isRunning()) {
setPaused(true);
result = r.get();
setPaused(false);
} else {
result = r.get();
}
return result != null ? result : defaultValue;
}
public boolean suspend() {
if (isRunning()) {
// System.out.println(getName() + " Suspended");
// Utility.printStackTrace();
setRun(false);
return true;
}
@ -213,8 +234,6 @@ public abstract class Device implements Reconfigurable {
public void resume() {
children.forEach(Device::resume);
if (!isRunning()) {
// System.out.println(getName() + " Resumed");
// Utility.printStackTrace();
setRun(true);
waitCycles = 0;
}

View File

@ -20,13 +20,12 @@ package jace.core;
import java.io.IOException;
import java.io.StringReader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.logging.Level;
import java.util.logging.Logger;
@ -96,8 +95,14 @@ public class Keyboard implements Reconfigurable {
private static final Map<KeyCode, Set<KeyHandler>> keyHandlersByKey = new HashMap<>();
private static final Map<Object, Set<KeyHandler>> keyHandlersByOwner = new HashMap<>();
public static void registerInvokableAction(InvokableAction action, Object owner, Method method, String code) {
boolean isStatic = Modifier.isStatic(method.getModifiers());
/**
*
* @param action
* @param owner
* @param method
* @param code
*/
public static void registerInvokableAction(InvokableAction action, Object owner, Function<Boolean, Boolean> method, String code) {
registerKeyHandler(new KeyHandler(code) {
@Override
public boolean handleKeyUp(KeyEvent e) {
@ -105,41 +110,40 @@ public class Keyboard implements Reconfigurable {
if (action == null || !action.notifyOnRelease()) {
return false;
}
// System.out.println("Key up: "+method.toString());
Object returnValue = null;
try {
if (method.getParameterCount() > 0) {
returnValue = method.invoke(isStatic ? null : owner, false);
} else {
returnValue = method.invoke(isStatic ? null : owner);
}
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
Logger.getLogger(Keyboard.class.getName()).log(Level.SEVERE, null, ex);
}
if (returnValue != null) {
return (Boolean) returnValue;
}
return action.consumeKeyEvent();
return method.apply(false) || action.consumeKeyEvent();
}
@Override
public boolean handleKeyDown(KeyEvent e) {
// System.out.println("Key down: "+method.toString());
Emulator.withComputer(c -> c.getKeyboard().shiftPressed = e.isShiftDown());
Object returnValue = null;
try {
if (method.getParameterCount() > 0) {
returnValue = method.invoke(isStatic ? null : owner, true);
} else {
returnValue = method.invoke(isStatic ? null : owner);
}
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
Logger.getLogger(Keyboard.class.getName()).log(Level.SEVERE, null, ex);
if (action == null) {
return false;
}
if (returnValue != null) {
return (Boolean) returnValue;
return method.apply(true) || action.consumeKeyEvent();
}
}, owner);
}
public static void registerInvokableAction(InvokableAction action, Object owner, BiFunction<Object, Boolean, Boolean> method, String code) {
registerKeyHandler(new KeyHandler(code) {
@Override
public boolean handleKeyUp(KeyEvent e) {
Emulator.withComputer(c -> c.getKeyboard().shiftPressed = e.isShiftDown());
if (action == null || !action.notifyOnRelease()) {
return false;
}
return action != null ? action.consumeKeyEvent() : null;
return method.apply(owner, false) || action.consumeKeyEvent();
}
@Override
public boolean handleKeyDown(KeyEvent e) {
// System.out.println("Key down: "+method.toString());
Emulator.withComputer(c -> c.getKeyboard().shiftPressed = e.isShiftDown());
if (action == null) {
return false;
}
return method.apply(owner, true) || action.consumeKeyEvent();
}
}, owner);
}

View File

@ -73,7 +73,7 @@ public abstract class TimedDevice extends Device {
if (!isRunning()) {
return false;
}
setPaused(true);
super.setPaused(true);
try {
// KLUDGE: Sleeping to wait for worker thread to hit paused state. We might be inside the worker (?)
Thread.sleep(10);
@ -82,6 +82,15 @@ public abstract class TimedDevice extends Device {
return true;
}
@Override
public void setPaused(boolean paused) {
if (!isPaused() && paused) {
pause();
} else {
super.setPaused(paused);
}
}
/**
* This is used in unit tests where we want the device
* to act like it is resumed, but not actual free-running.
@ -100,7 +109,7 @@ public abstract class TimedDevice extends Device {
return;
}
worker = new Thread(() -> {
System.out.println("Worker thread for " + getDeviceName() + " starting");
// System.out.println("Worker thread for " + getDeviceName() + " starting");
while (isRunning()) {
hasStopped = false;
doTick();
@ -113,7 +122,7 @@ public abstract class TimedDevice extends Device {
}
}
hasStopped = true;
System.out.println("Worker thread for " + getDeviceName() + " stopped");
// System.out.println("Worker thread for " + getDeviceName() + " stopped");
});
worker.setDaemon(false);
worker.setPriority(Thread.MAX_PRIORITY);

View File

@ -20,8 +20,6 @@ package jace.core;
import java.io.File;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@ -30,12 +28,10 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.function.Function;
import jace.config.Configuration;
import jace.config.InvokableAction;
import jace.config.InvokableActionRegistry;
import javafx.application.Platform;
import javafx.geometry.Pos;
import javafx.scene.control.Alert;
@ -406,38 +402,15 @@ public class Utility {
return null;
}
static final Map<InvokableAction, Runnable> allActions = new ConcurrentHashMap<>();
public static Map<InvokableAction, Runnable> getAllInvokableActions() {
synchronized (allActions) {
if (allActions.isEmpty()) {
Configuration.BASE.getTreeAsStream().forEach((Configuration.ConfigNode node) -> {
for (Method m : node.subject.getClass().getMethods()) {
if (m.isAnnotationPresent(InvokableAction.class)) {
allActions.put(m.getAnnotation(InvokableAction.class), () -> {
try {
m.invoke(node.subject);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
Logger.getLogger(Utility.class.getName()).log(Level.SEVERE, null, ex);
}
});
}
}
});
}
}
return allActions;
}
public static Runnable getNamedInvokableAction(String action) {
Map<InvokableAction, Runnable> actions = getAllInvokableActions();
List<InvokableAction> actionsList = new ArrayList(actions.keySet());
public static Function<Boolean, Boolean> getNamedInvokableAction(String action) {
InvokableActionRegistry registry = InvokableActionRegistry.getInstance();
List<InvokableAction> actionsList = new ArrayList(registry.getAllStaticActions());
actionsList.sort((a, b) -> Integer.compare(getActionNameMatch(action, a), getActionNameMatch(action, b)));
// for (InvokableAction a : actionsList) {
// String actionName = a.alternatives() == null ? a.name() : (a.name() + ";" + a.alternatives());
// System.out.println("Score for " + action + " evaluating " + a.name() + ": " + getActionNameMatch(action, a));
// }
return actions.get(actionsList.get(0));
return registry.getStaticFunction(actionsList.get(0).name());
}
private static int getActionNameMatch(String str, InvokableAction action) {

View File

@ -156,7 +156,7 @@ public abstract class Video extends Device {
public void tick() {
addWaitCycles(waitsPerCycle);
addWaitCycles((int) motherboardAdjustedWaitsPerCycle);
setScannerLocation(currentWriter.getYOffset(y));
if (y < APPLE_SCREEN_LINES) setScannerLocation(currentWriter.getYOffset(y));
setFloatingBus(computer.getMemory().readRaw(scannerAddress + x));
if (hPeriod > 0) {
hPeriod--;
@ -164,7 +164,7 @@ public abstract class Video extends Device {
x = -1;
}
} else {
if (!isVblank && x < APPLE_CYCLES_PER_LINE) {
if (!isVblank && x < APPLE_CYCLES_PER_LINE && x >= 0) {
draw();
}
if (x >= APPLE_CYCLES_PER_LINE - 1) {

View File

@ -18,16 +18,18 @@
*/
package jace.hardware.massStorage;
import jace.apple2e.MOS65C02;
import jace.core.Computer;
import jace.core.RAM;
import jace.hardware.ProdosDriver.MLI_COMMAND_TYPE;
import java.io.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.logging.Level;
import java.util.logging.Logger;
import jace.apple2e.MOS65C02;
import jace.core.Computer;
import jace.core.RAM;
import static jace.hardware.ProdosDriver.MLI_COMMAND;
import jace.hardware.ProdosDriver.MLI_COMMAND_TYPE;
import static jace.hardware.ProdosDriver.MLI_UNITNUMBER;
/**

View File

@ -15,6 +15,7 @@ import jace.apple2e.SoftSwitches;
import jace.apple2e.VideoNTSC;
import jace.cheat.Cheats;
import jace.config.ConfigurableField;
import jace.config.Configuration;
import jace.core.Video;
import jace.library.MediaConsumer;
@ -79,31 +80,31 @@ public class LawlessComputer extends Apple2e {
((VideoNTSC) getVideo()).enableVideo7 = false;
getMemory().configureActiveMemory();
getVideo().configureVideoMode();
Configuration.registerKeyHandlers();
doResume();
if (!performedBootAnimation) {
try {
performedBootAnimation = true;
waitForVBL();
renderWithMask(0, 0, 0, 0);
renderWithMask(0x0, 0x10, 0, 0x40, 0, 0x01, 0, 0x4);
renderWithMask(0x8, 0x10, 0x02, 0x40, 0, 0x01, 0x20, 0x4);
renderWithMask(0x8, 0x11, 0x22, 0x44);
renderWithMask(0x8, 0x19, 0x22, 0x66, 0x4c, 0x11, 0x33, 044);
renderWithMask(0x4c, 0x19, 0x33, 0x66);
renderWithMask(0x4c, 0x5d, 0x33, 0x77, 0x6e, 0x19, 0x3B, 0x66);
renderWithMask(0x6e, 0x5d, 0x3B, 0x77);
renderWithMask(0x6e, 0x7f, 0x3b, 0x7f, 0x7f, 0x5d, 0x7f, 0x77);
renderWithMask(0x7f, 0x7f, 0x7f, 0x7f);
renderWithMask(0x00,0x00,0x00,0x00);
renderWithMask(0x08,0x10,0x20,0x40,0x00,0x01,0x02,0x04);
renderWithMask(0x08,0x11,0x22,0x44);
renderWithMask(0x0C,0x19,0x32,0x64,0x48,0x11,0x23,0x46);
renderWithMask(0x4C,0x19,0x33,0x66);
renderWithMask(0x4E,0x1D,0x3B,0x76,0x6C,0x59,0x33,0x67);
renderWithMask(0x6E,0x5D,0x3B,0x77);
renderWithMask(0x7F,0x7E,0x7D,0x7B,0x77,0x6F,0x5F,0x3F);
renderWithMask(0x7F,0x7F,0x7F,0x7F);
waitForVBL(230);
renderWithMask(0x7f, 0x6e, 0x7f, 0x3b, 0x77, 0x6e, 0x5d, 0x3b);
renderWithMask(0x77, 0x6e, 0x5d, 0x3b);
renderWithMask(0x77, 0x66, 0x5d, 0x19, 0x33, 0x6e, 0x4c, 0x3b);
renderWithMask(0x33, 0x66, 0x4c, 0x19);
renderWithMask(0x33, 0x22, 0x4c, 0x8, 0x11, 0x66, 0x44, 0x19);
renderWithMask(0x11, 0x22, 0x44, 0x8);
renderWithMask(0x11, 0, 0x44, 0, 0, 0x22, 0, 0x8);
renderWithMask(0, 0, 0, 0);
renderWithMask(0x77,0x6F,0x5F,0x3F,0x7F,0x7E,0x7D,0x7B);
renderWithMask(0x77,0x6E,0x5D,0x3B);
renderWithMask(0x73,0x66,0x4D,0x1B,0x37,0x6E,0x5C,0x39);
renderWithMask(0x33,0x66,0x4C,0x19);
renderWithMask(0x31,0x62,0x44,0x09,0x13,0x26,0x4C,0x18);
renderWithMask(0x11,0x22,0x44,0x08);
renderWithMask(0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x00);
renderWithMask(0x00,0x00,0x00,0x00);
} catch (InterruptedException ex) {
Logger.getLogger(LawlessComputer.class.getName()).log(Level.SEVERE, null, ex);
}
@ -118,6 +119,15 @@ public class LawlessComputer extends Apple2e {
byte[] framebuffer = getBootScreen();
int maskOffset = 0;
for (int i = 0; i < 0x02000; i += 2) {
int y = Video.identifyHiresRow(i + 0x02000);
int x = i - Video.calculateHiresOffset(y);
if (y < 0 || y >= 192 || x >= 40 || x < 0) {
continue;
}
maskOffset = (y % 2) * 4;
maskOffset += ((x / 2) % 2) * 4;
maskOffset %= mask.length;
int next = (framebuffer[i] & 1) << 6;
Byte b1 = (byte) ((framebuffer[i + 0x02000] & 0x07f) >> 1 | next);
ram.getAuxMemory().writeByte(0x02000 + i, (byte) (b1 & mask[maskOffset] | 0x080));
@ -137,9 +147,6 @@ public class LawlessComputer extends Apple2e {
Byte b4 = (byte) ((framebuffer[i + 1] & 0x07f) >> 1 | next);
ram.getMainMemory().writeByte(0x02001 + i, (byte) (b4 & mask[maskOffset + 3] | 0x080));
}
if (i % 20 != 0) {
maskOffset = (maskOffset + 4) % mask.length;
}
}
Video.forceRefresh();
waitForVBL(5);

View File

@ -167,31 +167,27 @@ public class MetacheatUI {
@FXML
void loadCheats(ActionEvent event) {
Emulator.withComputer(c -> {
c.getMotherboard().whileSuspended(() -> {
FileChooser chooser = new FileChooser();
chooser.setTitle("Load cheats");
chooser.setInitialFileName("cheat.txt");
File saveFile = chooser.showOpenDialog(LawlessLegends.getApplication().primaryStage);
if (saveFile != null) {
cheatEngine.loadCheats(saveFile);
}
});
Emulator.whileSuspended(c-> {
FileChooser chooser = new FileChooser();
chooser.setTitle("Load cheats");
chooser.setInitialFileName("cheat.txt");
File saveFile = chooser.showOpenDialog(LawlessLegends.getApplication().primaryStage);
if (saveFile != null) {
cheatEngine.loadCheats(saveFile);
}
});
}
@FXML
void saveCheats(ActionEvent event) {
Emulator.withComputer(c -> {
c.getMotherboard().whileSuspended(() -> {
FileChooser chooser = new FileChooser();
chooser.setTitle("Save current cheats");
chooser.setInitialFileName("cheat.txt");
File saveFile = chooser.showSaveDialog(LawlessLegends.getApplication().primaryStage);
if (saveFile != null) {
cheatEngine.saveCheats(saveFile);
}
});
Emulator.whileSuspended(c-> {
FileChooser chooser = new FileChooser();
chooser.setTitle("Save current cheats");
chooser.setInitialFileName("cheat.txt");
File saveFile = chooser.showSaveDialog(LawlessLegends.getApplication().primaryStage);
if (saveFile != null) {
cheatEngine.saveCheats(saveFile);
}
});
}
@ -458,46 +454,44 @@ public class MetacheatUI {
if (cheatEngine == null) {
return;
}
Emulator.withComputer(c -> {
c.getMotherboard().whileSuspended(() -> {
if (animationTimer == null) {
animationTimer = new ScheduledThreadPoolExecutor(1);
}
Emulator.whileSuspended(c-> {
if (animationTimer == null) {
animationTimer = new ScheduledThreadPoolExecutor(1);
}
if (animationFuture != null) {
animationFuture.cancel(false);
}
if (animationFuture != null) {
animationFuture.cancel(false);
}
animationFuture = animationTimer.scheduleAtFixedRate(this::processMemoryViewUpdates, FRAME_RATE, FRAME_RATE, TimeUnit.MILLISECONDS);
animationFuture = animationTimer.scheduleAtFixedRate(this::processMemoryViewUpdates, FRAME_RATE, FRAME_RATE, TimeUnit.MILLISECONDS);
cheatEngine.initMemoryView();
int pixelsPerBlock = 16 * MEMORY_BOX_TOTAL_SIZE;
memoryViewColumns = (int) (memoryViewPane.getWidth() / pixelsPerBlock) * 16;
memoryViewRows = ((cheatEngine.getEndAddress() - cheatEngine.getStartAddress()) / memoryViewColumns) + 1;
double canvasHeight = memoryViewRows * MEMORY_BOX_TOTAL_SIZE * drawScale;
cheatEngine.initMemoryView();
int pixelsPerBlock = 16 * MEMORY_BOX_TOTAL_SIZE;
memoryViewColumns = (int) (memoryViewPane.getWidth() / pixelsPerBlock) * 16;
memoryViewRows = ((cheatEngine.getEndAddress() - cheatEngine.getStartAddress()) / memoryViewColumns) + 1;
double canvasHeight = memoryViewRows * MEMORY_BOX_TOTAL_SIZE * drawScale;
memoryViewContents.setPrefHeight(canvasHeight);
memoryViewCanvas.setHeight(canvasHeight);
GraphicsContext context = memoryViewCanvas.getGraphicsContext2D();
context.setFill(Color.rgb(40, 40, 40));
context.fillRect(0, 0, memoryViewCanvas.getWidth(), memoryViewCanvas.getHeight());
for (int addr = cheatEngine.getStartAddress(); addr <= cheatEngine.getEndAddress(); addr++) {
int col = (addr - cheatEngine.getStartAddress()) % memoryViewColumns;
int row = (addr - cheatEngine.getStartAddress()) / memoryViewColumns;
MemoryCell cell = cheatEngine.getMemoryCell(addr);
cell.setRect(
(int) (col * MEMORY_BOX_TOTAL_SIZE * drawScale),
(int) (row * MEMORY_BOX_TOTAL_SIZE * drawScale),
(int) (MEMORY_BOX_SIZE * drawScale),
(int) (MEMORY_BOX_SIZE * drawScale));
redrawNodes.add(cell);
}
MemoryCell.setListener((javafx.beans.value.ObservableValue<? extends jace.cheat.MemoryCell> prop, jace.cheat.MemoryCell oldCell, jace.cheat.MemoryCell newCell) -> {
redrawNodes.add(newCell);
});
setZoom(1/drawScale);
memoryViewContents.setPrefHeight(canvasHeight);
memoryViewCanvas.setHeight(canvasHeight);
GraphicsContext context = memoryViewCanvas.getGraphicsContext2D();
context.setFill(Color.rgb(40, 40, 40));
context.fillRect(0, 0, memoryViewCanvas.getWidth(), memoryViewCanvas.getHeight());
for (int addr = cheatEngine.getStartAddress(); addr <= cheatEngine.getEndAddress(); addr++) {
int col = (addr - cheatEngine.getStartAddress()) % memoryViewColumns;
int row = (addr - cheatEngine.getStartAddress()) / memoryViewColumns;
MemoryCell cell = cheatEngine.getMemoryCell(addr);
cell.setRect(
(int) (col * MEMORY_BOX_TOTAL_SIZE * drawScale),
(int) (row * MEMORY_BOX_TOTAL_SIZE * drawScale),
(int) (MEMORY_BOX_SIZE * drawScale),
(int) (MEMORY_BOX_SIZE * drawScale));
redrawNodes.add(cell);
}
MemoryCell.setListener((javafx.beans.value.ObservableValue<? extends jace.cheat.MemoryCell> prop, jace.cheat.MemoryCell oldCell, jace.cheat.MemoryCell newCell) -> {
redrawNodes.add(newCell);
});
setZoom(1/drawScale);
});
}

View File

@ -21,6 +21,7 @@ module lawlesslegends {
requires java.desktop;
requires java.datatransfer;
requires java.scripting;
requires static java.compiler;
requires javafx.fxmlEmpty;
requires javafx.fxml;
requires javafx.swing;
@ -46,5 +47,8 @@ module lawlesslegends {
uses javax.sound.sampled.SourceDataLine;
provides javax.annotation.processing.Processor with jace.config.InvokableActionAnnotationProcessor;
exports jace;
exports jace.config;
}

View File

@ -229,7 +229,11 @@
"name":"jace.apple2e.VideoDHGR"
},
{
"name":"jace.apple2e.VideoNTSC"
"name":"jace.apple2e.VideoNTSC",
"fields":[
{"name":"enableVideo7"},
{"name":"useTextPalette"}
]
},
{
"name":"jace.cheat.MetaCheat"
@ -309,21 +313,33 @@
{"name":"speedRatio"}
]
},
{
"name":"jace.core.Video",
"fields":[
{"name":"MIN_SCREEN_REFRESH"},
{"name":"hblankOffsetX"},
{"name":"hblankOffsetY"},
{"name":"waitsPerCycle"}
]
},
{
"name":"jace.core.VideoWriter"
},
{
"name":"jace.hardware.CardAppleMouse"
"name":"jace.hardware.CardAppleMouse",
"allPublicFields":true
},
{
"name":"jace.hardware.CardDiskII",
"allPublicFields":true,
"methods":[{"name":"<init>","parameterTypes":["jace.core.Computer"] }]
},
{
"name":"jace.hardware.CardExt80Col"
},
{
"name":"jace.hardware.CardHayesMicromodem"
"name":"jace.hardware.CardHayesMicromodem",
"allPublicFields":true
},
{
"name":"jace.hardware.CardMockingboard",
@ -331,17 +347,21 @@
},
{
"name":"jace.hardware.CardRamFactor",
"allPublicFields":true,
"methods":[{"name":"<init>","parameterTypes":["jace.core.Computer"] }]
},
{
"name":"jace.hardware.CardRamworks",
"allPublicFields":true,
"methods":[{"name":"<init>","parameterTypes":["jace.core.Computer"] }]
},
{
"name":"jace.hardware.CardSSC"
"name":"jace.hardware.CardSSC",
"allPublicFields":true
},
{
"name":"jace.hardware.CardThunderclock"
"name":"jace.hardware.CardThunderclock",
"allPublicFields":true
},
{
"name":"jace.hardware.Joystick",
@ -369,6 +389,7 @@
},
{
"name":"jace.hardware.massStorage.CardMassStorage",
"allPublicFields":true,
"methods":[{"name":"<init>","parameterTypes":["jace.core.Computer"] }]
},
{
@ -393,6 +414,7 @@
{"name":"newApplesoftBasicClicked","parameterTypes":["javafx.event.ActionEvent"] },
{"name":"newApplesoftBasicFromMemoryClicked","parameterTypes":["javafx.event.ActionEvent"] },
{"name":"newAssemblyListingClicked","parameterTypes":["javafx.event.ActionEvent"] },
{"name":"newHexdataClicked","parameterTypes":["javafx.event.ActionEvent"] },
{"name":"onCloseClicked","parameterTypes":["javafx.event.ActionEvent"] },
{"name":"testCompileClicked","parameterTypes":["javafx.event.ActionEvent"] },
{"name":"viewCompilerOutputClicked","parameterTypes":["javafx.event.ActionEvent"] }
@ -416,6 +438,7 @@
},
{
"name":"jace.lawless.LawlessVideo",
"allPublicFields":true,
"methods":[{"name":"<init>","parameterTypes":["jace.core.Computer"] }]
},
{
@ -907,7 +930,8 @@
"methods":[
{"name":"<init>","parameterTypes":[] },
{"name":"setVgrow","parameterTypes":["javafx.scene.Node","javafx.scene.layout.Priority"] }
]
],
"queriedMethods":[{"name":"getVgrow","parameterTypes":["javafx.scene.Node"] }]
},
{
"name":"javafx.scene.shape.LineTo"