All Downloads are FREE. Search and download functionalities are using the official Maven repository.

emulib.plugins.cpu.AbstractCPU Maven / Gradle / Ivy

/*
 * KISS, YAGNI, DRY
 *
 * (c) Copyright 2006-2017, Peter Jakubčo
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

package emulib.plugins.cpu;

import emulib.annotations.PluginType;
import net.jcip.annotations.ThreadSafe;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * This class implements some fundamental functionality that can be used by your own plug-ins. Such as:
 *
 * - support of breakpoints
 * - thread safe controlling of run states
 * - managing CPU state listeners
 *
 */
@ThreadSafe
@SuppressWarnings("unused")
public abstract class AbstractCPU implements CPU, Callable {
    private final static Logger LOGGER = LoggerFactory.getLogger(AbstractCPU.class);
    private final static Runnable EMPTY_TASK = () -> {};

    private final AtomicBoolean isDestroyed = new AtomicBoolean();
    private final ExecutorService eventReceiver = Executors.newSingleThreadExecutor();

    private final ExecutorService cpuExecutor = Executors.newSingleThreadExecutor();
    private final ExecutorService cpuStoppedWatcher = Executors.newSingleThreadExecutor();

    private final long pluginID;
    private final Set stateObservers = new CopyOnWriteArraySet<>();
    private final Set breakpoints = new ConcurrentSkipListSet<>();

    // ** CONTRACT: set only in "eventReceiver" or "cpuWatchTask" in a non-concurrent way **
    private volatile RunState runState = RunState.STATE_STOPPED_NORMAL;
    // set only in "execute" event
    private volatile CPUWatchTask cpuWatchTask;
    // ** END OF CONTRACT **

    private class CPUWatchTask implements Runnable {
        private final Future cpuFuture;

        private CPUWatchTask(Future cpuFuture) {
            this.cpuFuture = Objects.requireNonNull(cpuFuture);
        }

        @Override
        public void run() {
            try {
                runState = cpuFuture.get();
            } catch (ExecutionException e) {
                if (e.getCause() instanceof IndexOutOfBoundsException) {
                    runState = RunState.STATE_STOPPED_ADDR_FALLOUT;
                } else {
                    Throwable cause = e.getCause().getCause();
                    if (cause != null && (cause instanceof IndexOutOfBoundsException)) {
                        runState = RunState.STATE_STOPPED_ADDR_FALLOUT;
                    } else {
                        runState = RunState.STATE_STOPPED_BAD_INSTR;
                    }
                }
                LOGGER.error("Unexpected error during emulation", e);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                notifyStateChanged();
            }
        }

        void requestStop() {
            cpuFuture.cancel(true);
        }
    }

    /**
     * Creates new instance of CPU.
     *
     * @param pluginID plug-in identification number
     * @throws NullPointerException if pluginID is null
     */
    public AbstractCPU(Long pluginID) {
        this.pluginID = Objects.requireNonNull(pluginID);
    }

    /**
     * Get plug-in ID assigned by emuStudio.
     *
     * @return plug-in ID
     */
    protected long getPluginID() {
        return pluginID;
    }

    @Override
    public String getTitle() {
        return getClass().getAnnotation(PluginType.class).title();
    }

    /**
     * Does nothing.
     */
    @Override
    public void showSettings() {

    }

    /**
     * This class does not support showing settings
     *
     * @return false
     */
    @Override
    public boolean isShowSettingsSupported() {
        return false;
    }

    /**
     * This class support breakpoints.
     *
     * @return true
     */
    @Override
    public boolean isBreakpointSupported() {
        return true;
    }

    @Override
    public void setBreakpoint(int memLocation) {
        breakpoints.add(memLocation);
    }

    @Override
    public void unsetBreakpoint(int memLocation) {
        breakpoints.remove(memLocation);
    }

    @Override
    public boolean isBreakpointSet(int memLocation) {
        return breakpoints.contains(memLocation);
    }

    /**
     * Add new CPU listener to the list of stateObservers. CPU listener is an
     * implementation object of CPUListener interface. The methods are
     * called when some events are occured on CPU.
     *
     * @param listener CPUListener object
     * @return true if the listener was added, false otherwise
     */
    @Override
    public boolean addCPUListener(CPUListener listener) {
        return stateObservers.add(listener);
    }

    /**
     * Remove CPU listener object from the list of stateObservers. If the listener
     * is not included in the list, nothing will be done.
     *
     * @param listener CPUListener object
     * @return true if the listener was return, false otherwise
     */
    @Override
    public boolean removeCPUListener(CPUListener listener) {
        return stateObservers.remove(listener);
    }

    private void stopExecutor(ExecutorService executor) {
        Objects.requireNonNull(executor);
        executor.shutdown();
        try {
            executor.awaitTermination(10, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    @Override
    public void destroy() {
        if (isDestroyed.compareAndSet(false, true)) {
            try {
                stop();
                stopExecutor(eventReceiver);
                stopExecutor(cpuExecutor);
                stopExecutor(cpuStoppedWatcher);
                stateObservers.clear();
            } finally {
                destroyInternal();
            }
        }
    }

    /**
     * Called by original destroy() method. Do not override the original destroy() method. Subsequent calls of
     * destroy() will call this only once.
     */
    protected abstract void destroyInternal();

    private void waitForFuture(Future future) {
        Objects.requireNonNull(future);
        try {
            future.get();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } catch (ExecutionException e) {
            LOGGER.error("Unexpected error", e);
        }
    }

    private void notifyStateChanged() {
        final RunState tmpRunState = runState;
        stateObservers.forEach(observer -> {
            try {
                observer.runStateChanged(tmpRunState);
                observer.internalStateChanged();
            } catch (Exception e) {
                LOGGER.error("CPU Listener error", e);
            }
        });
    }

    private void ensureCpuIsStopped() {
        try {
            cpuStoppedWatcher.submit(EMPTY_TASK).get();
        } catch (ExecutionException e) {
            LOGGER.error("Unexpected error while waiting for CPU stop", e);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    @Override
    public void reset() { reset(0); }

    @Override
    public void reset(int addr) {
        Future future = eventReceiver.submit(() -> {
            requestStop();
            ensureCpuIsStopped();
            resetInternal(addr);
            runState = RunState.STATE_STOPPED_BREAK;
            notifyStateChanged();
        });
        waitForFuture(future);
    }

    @Override
    public void execute() {
        Future future = eventReceiver.submit(() -> {
            if (runState == RunState.STATE_STOPPED_BREAK) {
                runState = RunState.STATE_RUNNING;
                notifyStateChanged();

                Future cpuFuture = cpuExecutor.submit(AbstractCPU.this);
                cpuWatchTask = new CPUWatchTask(cpuFuture);
                cpuStoppedWatcher.submit(cpuWatchTask);
            }
        });
        waitForFuture(future);
    }

    @Override
    public void pause() {
        Future future = eventReceiver.submit(() -> {
            if (runState == RunState.STATE_RUNNING) {
                requestStop();
                ensureCpuIsStopped();
                if (runState == RunState.STATE_RUNNING || runState == RunState.STATE_STOPPED_NORMAL) {
                    runState = RunState.STATE_STOPPED_BREAK;
                }
                notifyStateChanged();
            }
        });
        waitForFuture(future);
    }

    @Override
    public void stop() {
        Future future = eventReceiver.submit(() -> {
            if (runState == RunState.STATE_STOPPED_BREAK || runState == RunState.STATE_RUNNING) {
                requestStop();
                ensureCpuIsStopped();
                if (runState == RunState.STATE_RUNNING || runState == RunState.STATE_STOPPED_BREAK) {
                    runState = RunState.STATE_STOPPED_NORMAL;
                }
                notifyStateChanged();
            }

        });
        waitForFuture(future);
    }

    @Override
    public void step() {
        Future future = eventReceiver.submit(() -> {
            if (runState == RunState.STATE_STOPPED_BREAK) {
                try {
                    runState = stepInternal();
                    if (runState == RunState.STATE_RUNNING) {
                        runState = RunState.STATE_STOPPED_BREAK;
                    }
                } catch (IndexOutOfBoundsException e) {
                    runState = RunState.STATE_STOPPED_ADDR_FALLOUT;
                    LOGGER.error("Unexpected error during emulation", e);
                } catch (Exception e) {
                    if (e.getCause() != null && e.getCause() instanceof IndexOutOfBoundsException) {
                        runState = RunState.STATE_STOPPED_ADDR_FALLOUT;
                    } else {
                        runState = RunState.STATE_STOPPED_BAD_INSTR;
                    }
                    LOGGER.error("Unexpected error during emulation", e);
                }
                notifyStateChanged();
            }
        });
        waitForFuture(future);
    }

    /**
     * Request CPU implementation about stopping the execution loop.
     * CONTRACT: run in event thread
     */
    private void requestStop() {
        CPUWatchTask tmpCpuWatchTask = cpuWatchTask;
        if (tmpCpuWatchTask != null) {
            tmpCpuWatchTask.requestStop();
        }
    }

    /**
     * Perform one emulation step in synchronized context.
     *
     * @return new CPU state. If nothing bad happened, it should return RunState.STATE_STOPPED_BREAK.
     * @throws Exception The emulator is allowed to throw any exception
     */
    protected abstract RunState stepInternal() throws Exception;

    /**
     * Performs specific CPU reset.
     *
     * CONTRACT: If this method throws an exception, the behavior is undefined.
     *
     * @param startPos starting position (similar to calling reset(pos))
     */
    protected abstract void resetInternal(int startPos);

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy