/* MachineBase.java (c) 2008-2015 Edward Swartz All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse Public License v1.0 which accompanies this distribution, and is available at http://www.eclipse.org/legal/epl-v10.html */ package v9t9.engine.machine; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.List; import java.util.Timer; import java.util.TimerTask; import v9t9.common.asm.IRawInstructionFactory; import v9t9.common.cassette.ICassetteChip; import v9t9.common.client.IClient; import v9t9.common.client.IEmulatorContentSourceProvider; import v9t9.common.client.IKeyboardHandler; import v9t9.common.client.ISettingsHandler; import v9t9.common.cpu.CpuMetrics; import v9t9.common.cpu.ICpu; import v9t9.common.cpu.ICpuMetrics; import v9t9.common.cpu.IExecutor; import v9t9.common.demos.IDemoHandler; import v9t9.common.demos.IDemoManager; import v9t9.common.dsr.IPrinterImageHandler; import v9t9.common.events.BaseEventNotifier; import v9t9.common.events.IEventNotifier; import v9t9.common.events.NotifyEvent; import v9t9.common.events.NotifyEvent.Level; import v9t9.common.events.NotifyException; import v9t9.common.files.DataFiles; import v9t9.common.files.IEmulatedFileHandler; import v9t9.common.files.IFileExecutionHandler; import v9t9.common.files.IPathFileLocator; import v9t9.common.files.PathFileLocator; import v9t9.common.hardware.ICruChip; import v9t9.common.hardware.ISoundChip; import v9t9.common.hardware.ISpeechChip; import v9t9.common.hardware.IVdpChip; import v9t9.common.keyboard.IKeyboardMapping; import v9t9.common.keyboard.IKeyboardModeListener; import v9t9.common.keyboard.IKeyboardState; import v9t9.common.keyboard.NullKeyboardHandler; import v9t9.common.machine.IMachine; import v9t9.common.machine.IMachineModel; import v9t9.common.machine.TerminatedException; import v9t9.common.memory.IMemory; import v9t9.common.memory.IMemoryDomain; import v9t9.common.memory.IMemoryEntry; import v9t9.common.memory.IMemoryModel; import v9t9.common.modules.IModuleDetector; import v9t9.common.modules.IModuleManager; import v9t9.common.settings.SettingSchemaProperty; import v9t9.engine.demos.DemoManager; import v9t9.engine.files.EmulatedFileHandler; import v9t9.engine.files.directory.DiskDirectoryMapper; import v9t9.engine.files.directory.EmuDiskSettings; import v9t9.engine.files.image.DiskImageMapper; import v9t9.engine.keyboard.KeyboardState; import v9t9.engine.memory.DiskMemoryEntry; import ejs.base.properties.IProperty; import ejs.base.properties.IPropertyListener; import ejs.base.settings.ISettingSection; import ejs.base.timer.FastTimer; import ejs.base.utils.ListenerList; import ejs.base.utils.ListenerList.IFire; /** Encapsulate all the information about a running emulated machine. * @author ejs */ abstract public class MachineBase implements IMachine { private volatile boolean alive; private final int cpuTicksPerSec = 100; protected IMemory memory; protected IMemoryDomain console; protected ICpu cpu; protected IExecutor executor; protected IClient client; protected Timer timer; protected FastTimer fastTimer; private IVdpChip vdp; protected boolean allowInterrupts; protected TimerTask clientTask; protected Runnable baseTimingTask; protected IMemoryModel memoryModel; protected TimerTask videoUpdateTask; protected Thread machineRunner; protected Thread videoRunner; protected int throttleCount; protected IKeyboardState keyboardState; protected ISoundChip sound; protected ISpeechChip speech; private ICpuMetrics cpuMetrics; protected TimerTask memorySaverTask; protected IModuleManager moduleManager; private ICruChip cru; private IRawInstructionFactory instructionFactory; private final IMachineModel machineModel; private IPropertyListener pauseListener; protected IProperty pauseMachine; private final ISettingsHandler settings; private IPathFileLocator locator; private IDemoHandler demoHandler; private IDemoManager demoManager; private IKeyboardHandler keyboardHandler; private IKeyboardMapping keyboardMapping; private ListenerList<IKeyboardModeListener> keyboardModeListeners; private IEmulatedFileHandler emulatedFileHandler; private ICassetteChip cassette; private IEventNotifier eventNotifier; private List<IEmulatorContentSourceProvider> contentProviders = new ArrayList<IEmulatorContentSourceProvider>(1); private List<IPrinterImageHandler> printerImageHandlers = new ArrayList<IPrinterImageHandler>(1); private IModuleDetector moduleDetector; public MachineBase(ISettingsHandler settings, IMachineModel machineModel) { this.settings = settings; pauseMachine = settings.get(settingPauseMachine); eventNotifier = new BaseEventNotifier(); this.machineModel = machineModel; locator = new PathFileLocator(); locator.setReadWritePathProperty(settings.get(DataFiles.settingStoredRamPath)); locator.addReadOnlyPathProperty(settings.get(DataFiles.settingBootRomsPath)); locator.addReadOnlyPathProperty(settings.get(DataFiles.settingUserRomsPath)); locator.addReadOnlyPathProperty(settings.get(DataFiles.settingShippingRomsPath)); try { if (getModel().getDataURL() != null) { // get refs to data files bundled in *.jar String modDbPath = getModel().getDataURL().toURI().toString(); List<String> list = new ArrayList<String>(2); list.add(modDbPath); list.add(modDbPath + "images/"); locator.addReadOnlyPathProperty(new SettingSchemaProperty("BuiltinPath", list)); } } catch (URISyntaxException e) { e.printStackTrace(); } this.memoryModel = machineModel.createMemoryModel(this); this.memory = memoryModel.getMemory(); this.console = memoryModel.getConsole(); timer = new Timer(true); fastTimer = new FastTimer("Machine"); keyboardModeListeners = new ListenerList<IKeyboardModeListener>(); init(machineModel); DiskDirectoryMapper fileMapper = new DiskDirectoryMapper(settings.get(EmuDiskSettings.emuDiskDsrEnabled)); DiskImageMapper imageMapper = new DiskImageMapper(settings); IFileExecutionHandler execHandler = createFileExecutionHandler(); emulatedFileHandler = new EmulatedFileHandler(settings, fileMapper, imageMapper, execHandler); machineModel.defineDevices(this); if (cpu != null) { cpuMetrics = new CpuMetrics(); executor = cpu.createExecutor(); executor.setMetrics(cpuMetrics); } pauseListener = new IPropertyListener() { public void propertyChanged(IProperty setting) { if (executor != null) executor.setExecuting(!setting.getBoolean()); } }; settings.get(settingPauseMachine).addListenerAndFire(pauseListener); //executor.addInstructionListener(new DebugConditionListener(cpu)); //executor.addInstructionListener(new DebugConditionListenerF99b(cpu)); } abstract protected IFileExecutionHandler createFileExecutionHandler(); /** * @return the settings */ public ISettingsHandler getSettings() { return settings; } protected void init(IMachineModel machineModel) { demoManager = new DemoManager(this); vdp = machineModel.createVdp(this); sound = machineModel.createSoundChip(this); speech = machineModel.createSpeechChip(this); cassette = machineModel.createCassetteChip(this); memoryModel.initMemory(this); moduleManager = machineModel.createModuleManager(this); cpu = machineModel.createCPU(this); keyboardState = new KeyboardState(this); keyboardHandler = new NullKeyboardHandler(keyboardState, this); this.instructionFactory = cpu != null ? cpu.getRawInstructionFactory() : null; } /* (non-Javadoc) * @see v9t9.emulator.common.IMachine#notifyEvent(v9t9.emulator.common.Level, java.lang.String) */ @Override public void notifyEvent(Level level, String string) { eventNotifier.notifyEvent(this, level, string); } /* (non-Javadoc) * @see v9t9.emulator.common.IMachine#notifyEvent(v9t9.emulator.common.NotifyEvent) */ @Override public void notifyEvent(NotifyEvent event) { eventNotifier.notifyEvent(event); } /* (non-Javadoc) * @see java.lang.Object#finalize() */ @Override protected void finalize() throws Throwable { pauseMachine.removeListener(pauseListener); client = null; memory = null; executor = null; super.finalize(); } /* (non-Javadoc) * @see v9t9.emulator.common.IMachine#getMemory() */ @Override public IMemory getMemory() { return memory; } /* (non-Javadoc) * @see v9t9.emulator.common.IMachine#isAlive() */ @Override public boolean isAlive() { return alive; } /* (non-Javadoc) * @see v9t9.emulator.common.IMachine#start() */ @Override public void start() { allowInterrupts = true; alive = true; if (client != null) client.start(); // the base machine running task baseTimingTask = new Runnable() { @Override public void run() { if (client != null) client.tick(); executor.queueTick(); } }; getFastMachineTimer().scheduleTask(baseTimingTask, getTicksPerSec()); executor.start(); } /* (non-Javadoc) * @see v9t9.emulator.common.IMachine#stop() */ @Override public void stop() { alive = false; executor.stop(); timer.cancel(); fastTimer.cancel(); memory.save(); throw new TerminatedException(); } /* (non-Javadoc) * @see v9t9.common.settings.IBaseMachine#reset() */ @Override public void reset() { boolean wasPaused = setPaused(true); keyboardState.resetKeyboard(); executor.getCompilerStrategy().reset(); unloadAndClear(); doReload(); if (cru != null) cru.reset(); cpu.reset(); setPaused(wasPaused); } /** * */ private void unloadAndClear() { // fully reload any ROMs in case they've changed IMemoryDomain domain = getMemory().getDomain(IMemoryDomain.NAME_CPU); for (IMemoryEntry entry : domain.getFlattenedMemoryEntries()) { if (entry.isVolatile()) { int addr = entry.mapAddress(entry.getAddr()); //System.out.println("Wiping " + entry + "@" + HexUtils.toHex4(addr)); for (int i = 0; i < entry.getSize(); i+= 2) domain.writeWord(i + addr, (short) 0); } else if (entry instanceof DiskMemoryEntry) { ((DiskMemoryEntry) entry).unload(); } } } /* (non-Javadoc) * @see v9t9.emulator.common.IMachine#getCpu() */ @Override public ICpu getCpu() { return cpu; } /* (non-Javadoc) * @see v9t9.emulator.common.IMachine#setCpu(v9t9.emulator.runtime.cpu.Cpu) */ @Override public void setCpu(ICpu cpu) { this.cpu = cpu; } /* (non-Javadoc) * @see v9t9.emulator.common.IMachine#getClient() */ @Override public IClient getClient() { return client; } /* (non-Javadoc) * @see v9t9.emulator.common.IMachine#setClient(v9t9.engine.Client) */ @Override public void setClient(IClient client) { this.client = client; } /* (non-Javadoc) * @see v9t9.emulator.common.IMachine#getExecutor() */ @Override public IExecutor getExecutor() { return executor; } /* (non-Javadoc) * @see v9t9.emulator.common.IMachine#setExecutor(v9t9.emulator.runtime.cpu.Executor) */ @Override public void setExecutor(IExecutor executor) { this.executor = executor; } /* (non-Javadoc) * @see v9t9.emulator.common.IMachine#getConsole() */ @Override public IMemoryDomain getConsole() { return console; } /* (non-Javadoc) * @see v9t9.emulator.common.IMachine#getMemoryModel() */ @Override public IMemoryModel getMemoryModel() { return memoryModel; } /* (non-Javadoc) * @see v9t9.emulator.common.IMachine#getKeyboardState() */ @Override public IKeyboardState getKeyboardState() { return keyboardState; } /* (non-Javadoc) * @see v9t9.emulator.common.IMachine#saveState(v9t9.base.core.settings.ISettingSection) */ @Override public synchronized void saveState(ISettingSection settings) { boolean wasExecuting = executor.setExecuting(false); boolean wasPaused = setPaused(true); settings.put("Class", getClass()); doSaveState(settings); this.settings.get(DataFiles.settingUserRomsPath).saveState(settings); ISettingSection workspace = settings.addSection("Workspace"); this.settings.getMachineSettings().save(workspace); //WorkspaceSettings.CURRENT.saveState(settings); setPaused(wasPaused); executor.setExecuting(wasExecuting); } protected void doSaveState(ISettingSection settings) { settings.put("MachineModel", machineModel.getIdentifier()); cpu.saveState(settings.addSection("CPU")); memory.saveState(settings.addSection("Memory")); vdp.saveState(settings.addSection("VDP")); if (sound != null) sound.saveState(settings.addSection("Sound")); if (speech != null) speech.saveState(settings.addSection("Speech")); if (moduleManager != null) moduleManager.saveState(settings.addSection("Modules")); if (cru != null) cru.saveState(settings.addSection("CRU")); } /* (non-Javadoc) * @see v9t9.emulator.common.IMachine#loadState(v9t9.base.core.settings.ISettingSection) */ @Override public synchronized void loadState(ISettingSection section) { /* machineRunner.interrupt(); videoRunner.interrupt(); timer.cancel(); cpuTimer.cancel(); videoTimer.cancel(); */ boolean wasExecuting = executor.setExecuting(false); settings.get(DataFiles.settingUserRomsPath).loadState(section); doLoadState(section); //Executor.settingDumpFullInstructions.setBoolean(true); //start(); executor.setExecuting(wasExecuting); } protected void doLoadState(ISettingSection section) { memory.getModel().resetMemory(); if (moduleManager != null) { moduleManager.loadState(section.getSection("Modules")); } memory.loadMemory(eventNotifier, section.getSection("Memory")); cpu.loadState(section.getSection("CPU")); vdp.loadState(section.getSection("VDP")); if (sound != null) sound.loadState(section.getSection("Sound")); if (speech != null) speech.loadState(section.getSection("Speech")); keyboardState.resetKeyboard(); keyboardState.resetJoystick(); if (cru != null) cru.loadState(section.getSection("CRU")); } /* (non-Javadoc) * @see v9t9.emulator.common.IMachine#getSound() */ @Override public ISoundChip getSound() { return sound; } /* (non-Javadoc) * @see v9t9.engine.machine.IMachine#getSpeech() */ @Override public ISpeechChip getSpeech() { return speech; } /* (non-Javadoc) * @see v9t9.common.machine.IMachine#getCassette() */ @Override public ICassetteChip getCassette() { return cassette; } /* (non-Javadoc) * @see v9t9.emulator.common.IMachine#getCpuTicksPerSec() */ @Override public int getTicksPerSec() { return cpuTicksPerSec; } /* (non-Javadoc) * @see v9t9.emulator.common.IMachine#isExecuting() */ @Override public boolean isExecuting() { return executor.isExecuting(); } /* (non-Javadoc) * @see v9t9.common.machine.IBaseMachine#isPaused() */ @Override public boolean isPaused() { return pauseMachine.getBoolean(); } /* (non-Javadoc) * @see v9t9.common.machine.IBaseMachine#setPaused(boolean) */ @Override public boolean setPaused(boolean paused) { boolean wasPaused = pauseMachine.getBoolean(); pauseMachine.setBoolean(paused); return wasPaused; } /* (non-Javadoc) * @see v9t9.emulator.common.IMachine#asyncExec(java.lang.Runnable) */ @Override public void asyncExec(Runnable runnable) { executor.asyncExec(runnable); } /* (non-Javadoc) * @see v9t9.emulator.common.IMachine#getCpuMetrics() */ @Override public ICpuMetrics getCpuMetrics() { return cpuMetrics; } public IModuleManager getModuleManager() { return moduleManager; } /* (non-Javadoc) * @see v9t9.emulator.common.IMachine#getVdp() */ @Override public IVdpChip getVdp() { return vdp; } /* (non-Javadoc) * @see v9t9.emulator.common.IMachine#getInstructionFactory() */ @Override public IRawInstructionFactory getInstructionFactory() { return instructionFactory; } /* (non-Javadoc) * @see v9t9.emulator.common.IMachine#getModel() */ @Override public IMachineModel getModel() { return machineModel; } /* (non-Javadoc) * @see v9t9.emulator.common.IMachine#keyStateChanged() */ @Override public void keyStateChanged() { } /* (non-Javadoc) * @see v9t9.emulator.common.IMachine#getMachineTimer() */ @Override public Timer getMachineTimer() { return timer; } /* (non-Javadoc) * @see v9t9.emulator.common.IBaseMachine#interrupt() */ @Override public void interrupt() { executor.interruptExecution(); } /** * @return the cruAccess */ public ICruChip getCru() { return cru; } /** * @param cru the cruAccess to set */ public void setCru(ICruChip cru) { this.cru = cru; } /* (non-Javadoc) * @see v9t9.common.machine.IMachine#getFileHandler() */ @Override public IEmulatedFileHandler getEmulatedFileHandler() { return emulatedFileHandler; } public void setFileHandler(IEmulatedFileHandler fileHandler) { this.emulatedFileHandler = fileHandler; } /* (non-Javadoc) * @see v9t9.common.machine.IMachine#getPathFileLocator() */ @Override public IPathFileLocator getRomPathFileLocator() { return locator; } /* (non-Javadoc) * @see v9t9.common.machine.IMachine#getEventNotifier() */ @Override public IEventNotifier getEventNotifier() { return eventNotifier; } /* (non-Javadoc) * @see v9t9.common.machine.IMachine#getDemoHandler() */ @Override public IDemoHandler getDemoHandler() { return demoHandler; } /** * @param demoHandler the demoHandler to set */ public void setDemoHandler(IDemoHandler demoHandler) { this.demoHandler = demoHandler; } /* (non-Javadoc) * @see v9t9.common.machine.IMachine#getDemoManager() */ @Override public IDemoManager getDemoManager() { return demoManager; } /* (non-Javadoc) * @see v9t9.common.machine.IMachine#setDemoManager(v9t9.common.demo.IDemoManager) */ @Override public void setDemoManager(IDemoManager manager) { this.demoManager = manager; } /* (non-Javadoc) * @see v9t9.common.machine.IBaseMachine#getFastMachineTimer() */ @Override public FastTimer getFastMachineTimer() { return fastTimer; } /* (non-Javadoc) * @see v9t9.common.machine.IMachine#getKeyboardMapping() */ @Override public IKeyboardMapping getKeyboardMapping() { return keyboardMapping; } /* (non-Javadoc) * @see v9t9.common.machine.IMachine#setKeyboardMapping(v9t9.common.keyboard.IKeyboardMapping) */ @Override public void setKeyboardMapping(IKeyboardMapping mapping) { this.keyboardMapping = mapping; } /* (non-Javadoc) * @see v9t9.common.machine.IMachine#setKeyboardHandler(v9t9.common.client.IKeyboardHandler) */ @Override public void setKeyboardHandler(IKeyboardHandler keyboardHandler) { this.keyboardHandler = keyboardHandler; } /* (non-Javadoc) * @see v9t9.common.machine.IMachine#getKeyboardHandler() */ @Override public IKeyboardHandler getKeyboardHandler() { return keyboardHandler; } /* (non-Javadoc) * @see v9t9.common.machine.IMachine#addKeyboardModeListener(v9t9.common.keyboard.IKeyboardModeListener) */ @Override public synchronized void addKeyboardModeListener(IKeyboardModeListener listener) { keyboardModeListeners.add(listener); if (!keyboardModeListeners.isEmpty()) { attachKeyboardModeListener(); } } /* (non-Javadoc) * @see v9t9.common.machine.IMachine#removeKeyboardModeListener(v9t9.common.keyboard.IKeyboardModeListener) */ @Override public synchronized void removeKeyboardModeListener(IKeyboardModeListener listener) { keyboardModeListeners.remove(listener); if (keyboardModeListeners.isEmpty()) { detachKeyboardModeListener(); } } /** * Start listening for keyboard mode changes * @see #fireKeyboardModeChanged(String) */ protected void attachKeyboardModeListener() { } /** * Stop listening for keyboard mode changes */ protected void detachKeyboardModeListener() { } protected void fireKeyboardModeChanged(final String modeId) { keyboardModeListeners.fire(new IFire<IKeyboardModeListener>() { @Override public void fire(IKeyboardModeListener listener) { listener.keyboardModeChanged(modeId); } }); } /* (non-Javadoc) * @see v9t9.common.machine.IMachine#getKeyboardMode() */ @Override public String getKeyboardMode() { return null; } /* (non-Javadoc) * @see v9t9.common.machine.IMachine#createModuleDetector(java.net.URI) */ @Override public IModuleDetector getModuleDetector() { if (moduleDetector == null) { moduleDetector = createModuleDetector(); } return moduleDetector; } /* (non-Javadoc) * @see v9t9.common.machine.IMachine#createModuleDetector() */ @Override public IModuleDetector createModuleDetector() { return null; } /* (non-Javadoc) * @see v9t9.common.machine.IMachine#reload() */ @Override public void reload() { doReload(); if (moduleManager != null) { try { moduleManager.restoreLastModule(); } catch (NotifyException e) { notifyEvent(e.getEvent()); } } } protected void doReload() { getExecutor().reset(); memory.reset(); if (client != null) { memoryModel.loadMemory(client.getEventNotifier()); } if (moduleManager != null) { moduleManager.reloadDatabase(); //moduleManager.reloadModules(client.getEventNotifier()); } } /* (non-Javadoc) * @see v9t9.common.machine.IMachine#getEmulatorContentProviders() */ @Override public IEmulatorContentSourceProvider[] getEmulatorContentProviders() { return (IEmulatorContentSourceProvider[]) contentProviders.toArray( new IEmulatorContentSourceProvider[contentProviders.size()]); } /* (non-Javadoc) * @see v9t9.common.machine.IMachine#addEmulatorContentProvider(v9t9.common.client.IEmulatorContentProvider) */ @Override public void addEmulatorContentProvider(IEmulatorContentSourceProvider provider) { if (!contentProviders.contains(provider)) contentProviders.add(provider); } public void addPrinterImageHandler(IPrinterImageHandler handler) { printerImageHandlers.add(handler); } /* (non-Javadoc) * @see v9t9.common.machine.IMachine#getRS232Handlers() */ @Override public IPrinterImageHandler[] getPrinterImageHandlers() { return printerImageHandlers.toArray(new IPrinterImageHandler[printerImageHandlers.size()]); } }