/* Author: Jeff Dalton * Updated: Fri Apr 13 00:58:51 2001 by Jeff Dalton * Copyright: (c) 2000, AIAI, University of Edinburgh */ package ix.examples; import java.util.*; import ix.util.*; import ix.util.lisp.*; /** * A simple example using an I-X framework. This example shows how * to extend PicoIX for a particular application type, in this case * discrete event simulation. */ public class PicoISim extends PicoIX { static Symbol SYM(String name) { // Abbreviation return Symbol.intern(name); } /** * An IX_System for discrete event simulation. */ static class IX_Simulator extends IX_System { long simTime = -1; // so it notes a change to 0 boolean stopWhenIdle = true; IX_Simulator() { controller = new SimController(this); model = new ModelManager(this); addIssueHandlers(makeBuiltinIssueHandlers()); addSimEventHandlers(makeBuiltinSimEventHandlers()); } IX_Simulator(Controller c, ModelManager m) { super(c != null ? c : new SimController(), m); addIssueHandlers(makeBuiltinIssueHandlers()); addSimEventHandlers(makeBuiltinSimEventHandlers()); } public void addSimEventHandlers(Object[] issueHandlers) { ((SimController)controller).installSimEventHandlers(issueHandlers); } public void setStopWhenIdle(boolean newValue) { stopWhenIdle = newValue; } public void schedule(SimEvent e) { // This method hides the existence of "schedule" issues. newEvent(new Issue(SYM("schedule"), e)); } public void start() { setSimTime(0); super.start(); } void setSimTime(long t) { if (t > simTime) { // there's no going back Debug.noteln("\n- - - - - time now " + t + " - - - - -\n"); simTime = t; } } void postSimEvent(SimEvent e) { ((SimController)controller).addSimEvent(e); } Object[] makeBuiltinIssueHandlers() { return new Object[] { new IssueHandler(SYM("schedule")) { void handleIssue(Issue i) { SimEvent e = (SimEvent)i.object; ((IX_Simulator)system).postSimEvent(e); } } }; } Object[] makeBuiltinSimEventHandlers() { return new Object[] { }; } } /** * The controller for a simulator handles an agenda of simulated * events as well as the agenda of issues that the simulator addresses * as an agent. */ static class SimController extends Controller { LListCollector simEvents = new LListCollector(); Hashtable simHandlerTable = new Hashtable(); SimController() {} SimController(IX_System system) { super(system); } void addSimEvent(SimEvent e) { Debug.noteln("Scheduling", e); simEvents.insertElement(e, EARLIER_EST_P); } static final Predicate2 EARLIER_EST_P = new Predicate2() { public boolean trueOf(Object a, Object b) { return ((SimEvent)a).est < ((SimEvent)b).est; } }; void mainLoop() { // Do this forever. while (true) { // Convert any external events to Issues. while (q.hasMessage()) { addEventIssue(q); } // If there's an issue, handle it; // else if we can execute a SimEvent, do that; // otherwise wait for an external event // unless we're supposed to stop when idle. if (!issues.isEmpty()) { handleIssue(selectIssue()); } else if (!simEvents.isEmpty()) { boolean didSomething = simulateIfPossible(); // If we get here and didn't do anything, we'll keep // being unable to do anything until some external // event makes a change that lets us progress, // so we may as well wait for an event rather than // keep looping. if (!didSomething) { q.waitForMessage(); } } else { whenIdle(); } } } void whenIdle() { // Subclasses may want to make it do something more interesting. if (((IX_Simulator)system).stopWhenIdle) { Debug.noteln("Simulator stopping because nothing to do"); system.stop(); Debug.assert(false, "continued after thread stop()"); } else { q.waitForMessage(); } } boolean simulateIfPossible() { Debug.noteln("Agenda", simEvents.contents()); for(LList el = simEvents.contents(); el != Lisp.NIL; el = el.cdr()) { SimEvent e = (SimEvent)el.car(); if (canSimulate(e)) { return simulateIfPossible(e); } } return false; } boolean simulateIfPossible(SimEvent e) { // This always simulates, but subclasses may have different ideas. simEvents.deleteElement(e); setSimTime(e.est); simulate(e); return true; } boolean canSimulate(SimEvent e) { SimEventHandler h = findSimHandler(e); // return h.isAble(e); if (h.isAble(e)) { return true; } else { Debug.noteln("Can't execute " + e + " at this time"); return false; } } void simulate(SimEvent e) { SimEventHandler h = findSimHandler(e); h.handleIssue(e); } void installSimEventHandlers(Object[] issueHandlers) { installIssueHandlers(issueHandlers, simHandlerTable); } SimEventHandler findSimHandler(Issue i) { SimEventHandler h = (SimEventHandler)simHandlerTable.get(i.verb); Debug.assert(h != null, "no sim handler for", i.verb); return h; } long getSimTime() { return ((IX_Simulator)system).simTime; } // It's the controller that sets the sim time because different // controllers might handle simulated time in different ways, // for instance by using scaled real time. void setSimTime(long t) { ((IX_Simulator)system).setSimTime(t); } } /** * Issues that represent scheduled events in the simulation. */ static class SimEvent extends Issue { long est = 0; SimEvent(Object verb, Object object, long when) { super(verb, object); this.est = when; } void execute(IX_Simulator simulator) { // The default does nothing. // The simulator is passed as an argument to allow the // method to access other parts of the system. } public String toString() { return "SimEvent[" + Lisp.printToString(verb) + " " + Lisp.printToString(object) + " at " + est + "]"; } } /** * Handlers for SimEvents. */ static abstract class SimEventHandler extends IssueHandler { SimEventHandler(Object verb) { super(verb); } boolean isAble(SimEvent e) { return true; } // Handy methods for use in SimEvent handlers. long getSimTime() { return ((IX_Simulator)system).simTime; } void postSimEvent(SimEvent e) { ((IX_Simulator)system).postSimEvent(e); } } } // Issues: // * Division of labour between Issue and IssueHandler methods. // * Could the handler just be a method of the Issue? // * Should isAble(SimEvent s) be a handler method, or should // we have isAble(SimEventHandler h) be a method of SimEvent? // (The handler is involved because it knows that the system is.) // * Should SimEvent implement Runnable? Don't want to imply that // all will work unproblematically if they're run in a separate // thread. // * Straighten out what should be public, protected, etc. // * A simulator could be oriented around objects rather than events. // How would that fit into the framework?