/*******************************************************************************
* Copyright (c) 2003-2005, 2013 Till Zoppke.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*
* Contributors:
* Till Zoppke - initial API and implementation
******************************************************************************/
/*
* Created on 02.04.2004
*/
package eniac.data.model;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import eniac.Manager;
import eniac.simulation.EEvent;
import eniac.simulation.EEventListener;
import eniac.simulation.EventQueue;
import eniac.simulation.Frequency;
import eniac.util.Status;
import eniac.util.StatusListener;
/**
* @author zoppke
*/
public class CyclingLights extends EData implements Runnable, EEventListener, StatusListener, Observer {
// ===================== eeventmanager fields //=======================
// length of a single and an addition-cycle
public static final int SINGLE_CYCLE = 10;
public static final int ADDITION_CYCLE = 200;
public static final int OFFSET_10P = 3;
public static final int CPP_TIME = 170;
// initial length and grow rate of event listeners
private static final int ARRAY_LENGTH = 3;
// prototype events for one addition cycle to be reused
private EEvent[] _events;
// 2-dim array of listeners
private EEventListener[][] _listeners;
private int[] _counters;
// ================= simulator fields //==============
private static final int ITERATION_0 = 0;
private static final int ITERATION_1 = 1;
private static final int ITERATION_2 = 2;
private static final int ITERATION_INFINITY = 3;
// flag indicating whether we should go on simulating.
private Thread _thread;
// maximum time until that events should be processed.
// if there is continuous simulation (iteration-switch==infinity),
// this will be Long.MAX_VALUE
private long _stopTime = 0;
// event queue to store our events
private EventQueue _queue;
// frequency we try to maintain during simulation
private Frequency _frequency;
// timestamps for computing the appropriate sleeping time between processing
// two events
private long _realTimestamp;
private long _simTimestamp;
// carry clear gate
private boolean _ccg;
// =============================== lifecycle
// ================================
/**
* @param type
*/
public CyclingLights() {
// empty constructor
}
public void init() {
super.init();
// add as listener to heaters of cycling unit
// EData unit =
// getConfiguration().getGarten().getKind(EType.CYCLING_UNIT, 0);
// Switch heaters = ((Unit) unit).getHeaters();
// heaters.addObserver(this);
// add to starter as busyListener
Status.LIFECYCLE.addListener(this);
// init eventqueue
_queue = new EventQueue();
// create and start simulation thread
_thread = new Thread(this);
_thread.start();
// add ourself as listener to be notified, when new events should be
// inserted.
addEEventListener(this, EEvent.GENERATE_NEW);
addEEventListener(this, EEvent.CCG_UP);
addEEventListener(this, EEvent.CCG_DOWN);
}
public void dispose() {
super.dispose();
// if there are threads waiting, stop them.
synchronized (this) {
_thread = null;
notifyAll();
}
}
// =========================== private methods
// ==============================
private synchronized void simulate() {
// check if there is work to do
while (_queue.isEmpty() || Status.LIFECYCLE.getValue() != Manager.LifeCycle.RUNNING
|| _queue.getFirst().time > _stopTime) {
// if nothing to do, wait for new work
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
// check, if we should should stop simulation.
// this will occure, when lifecycle is set to stopping
if (_thread == null) {
return;
}
}
// remove the first event from the queue
EEvent ev = _queue.removeFirst();
// update time
Status.SIMULATION_TIME.setValue(ev.time);
// process event
// check, if we have a listener specified
// (this is the alarm clock case)
if (ev.listener != null) {
ev.listener.process(ev);
}
else {
// otherwise look for registered listeners in our arrays.
EEventListener[] array = _listeners[ev.type];
for (int i = 0; i < _counters[ev.type]; ++i) {
array[i].process(ev);
}
}
// maybe sleep in order to maintain frequency.
// don't sleep, if the following event has the same timestamp.
if (_queue.getFirst().time > ev.time) {
// compute time differences
long realDiff = System.currentTimeMillis() - _realTimestamp;
long simDiff = ev.time - _simTimestamp;
// compute sleeping time
long sleepTime = (long) (simToReal(simDiff) / _frequency.linear() - realDiff);
// only sleep, if millis are positive
if (sleepTime > 0) {
try {
wait(sleepTime);
} catch (InterruptedException e) {
// we should not be interrupted. In case of program exit,
// we should be notified by propertyChange.
e.printStackTrace();
}
}
// adjust timestamps
long realTime = System.currentTimeMillis();
if (realTime - _realTimestamp > 100) {
_realTimestamp = realTime;
_simTimestamp = ev.time;
}
}
}
// ========================== public methods //=============================
public void initEvents() {
// init events.
// create list for easy initialization
List<EEvent> list = new LinkedList<>();
// CPP
list.add(new EEvent(CPP_TIME, EEvent.CPP));
// 10P
for (int i = 0; i < 10; ++i) {
long time = i * SINGLE_CYCLE + OFFSET_10P;
EEvent e = new EEvent(time, EEvent.PULSE_10P);
list.add(e);
}
// 9P
for (int i = 1; i < 10; ++i) {
long time = i * SINGLE_CYCLE;
EEvent e = new EEvent(time, EEvent.PULSE_9P);
list.add(e);
}
// 1P
list.add(new EEvent(10, EEvent.PULSE_1P));
// 2P
list.add(new EEvent(20, EEvent.PULSE_2P));
list.add(new EEvent(30, EEvent.PULSE_2P));
// 2'P
list.add(new EEvent(40, EEvent.PULSE_2AP));
list.add(new EEvent(50, EEvent.PULSE_2AP));
// 4P
list.add(new EEvent(60, EEvent.PULSE_4P));
list.add(new EEvent(70, EEvent.PULSE_4P));
list.add(new EEvent(80, EEvent.PULSE_4P));
list.add(new EEvent(90, EEvent.PULSE_4P));
// 1*P
list.add(new EEvent(100, EEvent.PULSE_1AP));
// CCG
list.add(new EEvent(110, EEvent.CCG_UP));
list.add(new EEvent(180, EEvent.CCG_DOWN));
// RP
list.add(new EEvent(130, EEvent.RP));
list.add(new EEvent(190, EEvent.RP));
// GENERATE_NEW
list.add(new EEvent(ADDITION_CYCLE, EEvent.GENERATE_NEW));
// NOP
for (int i = 0; i < 20; ++i) {
list.add(new EEvent(i * 10 + 6, EEvent.NOP));
}
for (int i = 10; i < 20; ++i) {
list.add(new EEvent(i * 10 + 3, EEvent.NOP));
list.add(new EEvent(i * 10 + 6, EEvent.NOP));
}
list.add(new EEvent(120, EEvent.NOP));
list.add(new EEvent(140, EEvent.NOP));
list.add(new EEvent(150, EEvent.NOP));
list.add(new EEvent(160, EEvent.NOP));
// init events array from list
_events = new EEvent[list.size()];
Collections.shuffle(list);
list.toArray(_events);
// insert events to simulator
insertEvents(_events);
}
public void run() {
while (_thread != null) {
simulate();
}
}
public synchronized void updateStopTime(int iterationMode) {
switch (iterationMode) {
case ITERATION_0 :
// debug mode. Set stoptime to time of next event
_stopTime = _queue.getFirst().time;
break;
case ITERATION_1 :
// single single step mode. Icrease stoptime by single cycle.
_stopTime += SINGLE_CYCLE;
break;
case ITERATION_2 :
// addition cycle step mode. Increase stoptime by addition
// cycle.
_stopTime += ADDITION_CYCLE;
break;
case ITERATION_INFINITY :
// infinity. Nothing to do.
break;
}
notifyAll();
}
public synchronized void setStopTime(long time) {
_stopTime = time;
notifyAll();
}
public synchronized void insertEvent(EEvent e) {
_queue.insert(e);
notifyAll();
}
public synchronized void insertEvents(EEvent[] events) {
_queue.insert(events);
notifyAll();
}
public synchronized void reset() {
_queue.empty();
_stopTime = 0L;
// TODO: this is a hack, because there was a NuPoExc when changing
// configuration. Find a proper way to init and dispose.
if (Status.LIFECYCLE.getValue() == Manager.LifeCycle.RUNNING) {
Status.SIMULATION_TIME.setValue(0L);
}
notifyAll();
}
public synchronized void setWantedFrequency(Frequency f) {
_frequency = f;
// notify because maybe our thread is sleeping for a long time
// and in case of speed up we want to process events now.
notifyAll();
}
public static long simToReal(long simTime) {
return simTime * 5;
}
public void setAlarmClock(long time, EEventListener listener) {
// compute next time slot for alarm
if (time % 10 == 6) {
time += 4;
}
else {
time += 3;
}
// create and insert event
EEvent e = new EEvent(time, EEvent.ALARM, listener);
insertEvent(e);
}
public void addEEventListener(EEventListener listener, short eventType) {
// check if initialized
if (_listeners == null) {
// init eeventListener array.
// recurse on all event types
Field[] fields = EEvent.class.getFields();
int max = 0;
for (int i = 0; i < fields.length; ++i) {
if (Modifier.isFinal(fields[i].getModifiers())) {
try {
max = Math.max(max, fields[i].getInt(null));
} catch (Exception e) {
e.printStackTrace();
}
}
}
// length of arrays is maximum key + 1
max += 1;
_counters = new int[max];
_listeners = new EEventListener[max][];
for (int i = 0; i < _listeners.length; ++i) {
_listeners[i] = new EEventListener[ARRAY_LENGTH];
}
}
// check size
int oldLength = _listeners[eventType].length;
if (oldLength <= _counters[eventType]) {
EEventListener[] temp = new EEventListener[oldLength + ARRAY_LENGTH];
System.arraycopy(_listeners[eventType], 0, temp, 0, oldLength);
_listeners[eventType] = temp;
}
// add listener
_listeners[eventType][_counters[eventType]++] = listener;
}
public boolean isCCG() {
return _ccg;
}
// ========================== eevent processing //==========================
/**
* @param e
* @see eniac.simulation.EEventListener#process(eniac.simulation.EEvent)
*/
public void process(EEvent e) {
// switch on the event type
switch (e.type) {
case EEvent.GENERATE_NEW :
// adjust events for the new addition cycle
for (int i = 0; i < _events.length; ++i) {
_events[i].time += ADDITION_CYCLE;
}
// insert new events to the simulator.
insertEvents(_events);
break;
case EEvent.CCG_UP :
_ccg = true;
break;
case EEvent.CCG_DOWN :
_ccg = false;
}
}
@Override
public synchronized void statusChanged(Status status, Object value) {
// just notify simulation thread. This is useful, when the thread
// is waiting for the runlevel to idle and go back to work.
notifyAll();
}
/**
* @param o
* @param arg
* @see java.util.Observer#update(java.util.Observable, java.lang.Object)
*/
public void update(Observable o, Object arg) {
// adjust simulator and event processing to the new state of power
// if (hasPower()) {
// // if power on, then init simulator by inserting first set of events
// getConfiguration().getCyclingLights().initEvents();
// // update iteration. This will take place in case of infinity.
// updateIteration();
// } else {
// // if power off, then reset simulator and array of events for reuse
// getConfiguration().getCyclingLights().reset();
// }
}
}