/* * Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301 USA */ package jace.core; import jace.apple2e.SoftSwitches; import jace.apple2e.Speaker; import jace.config.ConfigurableField; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Optional; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; /** * Motherboard is the heart of the computer. It can have a list of cards * inserted (the behavior and number of cards is determined by the Memory class) * as well as a speaker and any other miscellaneous devices (e.g. joysticks). * This class provides the real main loop of the emulator, and is responsible * for all timing as well as the pause/resume features used to prevent resource * collisions between threads. Created on May 1, 2007, 11:22 PM * * @author Brendan Robert (BLuRry) brendan.robert@gmail.com */ public class Motherboard extends TimedDevice { final public Set<Device> miscDevices = new LinkedHashSet<>(); @ConfigurableField(name = "Enable Speaker", shortName = "speaker", defaultValue = "true") public static boolean enableSpeaker = true; public Speaker speaker; void vblankEnd() { SoftSwitches.VBL.getSwitch().setState(true); computer.notifyVBLStateChanged(true); } void vblankStart() { SoftSwitches.VBL.getSwitch().setState(false); computer.notifyVBLStateChanged(false); } /** * Creates a new instance of Motherboard * @param computer * @param oldMotherboard */ public Motherboard(Computer computer, Motherboard oldMotherboard) { super(computer); if (oldMotherboard != null) { miscDevices.addAll(oldMotherboard.miscDevices); speaker = oldMotherboard.speaker; } } @Override protected String getDeviceName() { return "Motherboard"; } @Override public String getShortName() { return "mb"; } @ConfigurableField(category = "advanced", name = "CPU per clock", defaultValue = "1", description = "Number of CPU cycles per clock cycle (normal = 1)") public static int cpuPerClock = 1; public int clockCounter = 1; @Override public void tick() { Optional<Card>[] cards = computer.getMemory().getAllCards(); try { clockCounter--; computer.getCpu().doTick(); if (clockCounter > 0) { return; } clockCounter = cpuPerClock; computer.getVideo().doTick(); for (Optional<Card> card : cards) { card.ifPresent(c -> c.doTick()); } miscDevices.stream().forEach((m) -> { m.doTick(); }); } catch (Throwable t) { Logger.getLogger(getClass().getName()).log(Level.SEVERE, null, t); } } // From the holy word of Sather 3:5 (Table 3.1) :-) // This average speed averages in the "long" cycles public static long SPEED = 1020484L; // (NTSC) //public static long SPEED = 1015625L; // (PAL) @Override public long defaultCyclesPerSecond() { return SPEED; } @Override public synchronized void reconfigure() { boolean startAgain = pause(); accelorationRequestors.clear(); super.reconfigure(); // Now create devices as needed, e.g. sound if (enableSpeaker) { try { if (speaker == null) { speaker = new Speaker(computer); if (computer.mixer.lineAvailable) { speaker.attach(); miscDevices.add(speaker); } else { System.out.print("No lines available! Speaker not running."); } } speaker.reconfigure(); } catch (Throwable t) { System.out.println("Unable to initalize sound -- deactivating speaker out"); speaker.detach(); miscDevices.remove(speaker); } } else { System.out.println("Speaker not enabled, leaving it off."); if (speaker != null) { speaker.detach(); miscDevices.remove(speaker); } } if (startAgain && computer.getMemory() != null) { resume(); } } static HashSet<Object> accelorationRequestors = new HashSet<>(); public void requestSpeed(Object requester) { accelorationRequestors.add(requester); enableTempMaxSpeed(); } public void cancelSpeedRequest(Object requester) { accelorationRequestors.remove(requester); if (accelorationRequestors.isEmpty()) { disableTempMaxSpeed(); } } @Override public void attach() { } final Set<Card> resume = new HashSet<>(); @Override public boolean suspend() { synchronized (resume) { resume.clear(); for (Optional<Card> c : computer.getMemory().getAllCards()) { if (!c.isPresent()) { continue; } if (!c.get().suspendWithCPU() || !c.get().isRunning()) { continue; } if (c.get().suspend()) { resume.add(c.get()); } } } return super.suspend(); } @Override public void resume() { super.resume(); synchronized (resume) { resume.stream().forEach((c) -> { c.resume(); }); } } @Override public void detach() { System.out.println("Detaching motherboard"); miscDevices.stream().forEach((d) -> { d.suspend(); d.detach(); }); miscDevices.clear(); // halt(); super.detach(); } }