event-based programming · things happen and when they happen, you want to know about it user...
TRANSCRIPT
Event-based programmingDan S. Wallach and Mack Joyner, Rice University
Copyright © 2016 Dan Wallach, All Rights Reserved
Where’s the week10 code dump?
Should be online this afternoon If it makes you feel any better, your project this week is “from scratch”, so not much will be there beyond what you’ve already seen.
What’s an event?
Things happenAnd when they happen, you want to know about it
User interface programming Button clicks, mouse motion, keyboard input, window resize, …
Network programming Message arrival, connection success/failure, network status change, …
And more Clock “ticks”, battery/charging status, …
Common theme: when a thing happens, please call me!
Vocabulary
Callbacks, Observers, Listeners, EventHandlers, … Same basic idea: you register some code to run when something happens
This stuff is used everywherejava.util Interface EventListener
All Known Subinterfaces: Action, ActionListener, AdjustmentListener, AncestorListener, AWTEventListener, BeanContextMembershipListener, BeanContextServiceRevokedListener, BeanContextServices, BeanContextServicesListener,CaretListener, CellEditorListener, ChangeListener, ComponentListener, ConnectionEventListener, ContainerListener, ControllerEventListener, DocumentListener, DragGestureListener, DragSourceListener,DragSourceMotionListener, DropTargetListener, FlavorListener, FocusListener, HandshakeCompletedListener, HierarchyBoundsListener, HierarchyListener, HyperlinkListener, IIOReadProgressListener,IIOReadUpdateListener, IIOReadWarningListener, IIOWriteProgressListener, IIOWriteWarningListener, InputMethodListener, InternalFrameListener, ItemListener, KeyListener, LineListener, ListDataListener,ListSelectionListener, MenuDragMouseListener, MenuKeyListener, MenuListener, MetaEventListener, MouseInputListener, MouseListener, MouseMotionListener, MouseWheelListener, NamespaceChangeListener,NamingListener, NodeChangeListener, NotificationListener, ObjectChangeListener, PopupMenuListener, PreferenceChangeListener, PropertyChangeListener, RowSetListener, RowSorterListener,SSLSessionBindingListener, StatementEventListener, TableColumnModelListener, TableModelListener, TextListener, TreeExpansionListener, TreeModelListener, TreeSelectionListener, TreeWillExpandListener,UndoableEditListener, UnsolicitedNotificationListener, VetoableChangeListener, WindowFocusListener, WindowListener, WindowStateListener All Known Implementing Classes: AbstractAction, AbstractButton.ButtonChangeListener, AWTEventListenerProxy, AWTEventMulticaster, BasicButtonListener, BasicColorChooserUI.PropertyHandler, BasicComboBoxEditor,BasicComboBoxEditor.UIResource, BasicComboBoxUI.FocusHandler, BasicComboBoxUI.ItemHandler, BasicComboBoxUI.KeyHandler, BasicComboBoxUI.ListDataHandler, BasicComboBoxUI.PropertyChangeHandler,BasicComboPopup.InvocationKeyHandler, BasicComboPopup.InvocationMouseHandler, BasicComboPopup.InvocationMouseMotionHandler, BasicComboPopup.ItemHandler, BasicComboPopup.ListDataHandler,BasicComboPopup.ListMouseHandler, BasicComboPopup.ListMouseMotionHandler, BasicComboPopup.ListSelectionHandler, BasicComboPopup.PropertyChangeHandler, BasicDesktopIconUI.MouseInputHandler,BasicDesktopPaneUI.CloseAction, BasicDesktopPaneUI.MaximizeAction, BasicDesktopPaneUI.MinimizeAction, BasicDesktopPaneUI.NavigateAction, BasicDesktopPaneUI.OpenAction, BasicDirectoryModel,BasicFileChooserUI.ApproveSelectionAction, BasicFileChooserUI.CancelSelectionAction, BasicFileChooserUI.ChangeToParentDirectoryAction, BasicFileChooserUI.DoubleClickListener,BasicFileChooserUI.GoHomeAction, BasicFileChooserUI.NewFolderAction, BasicFileChooserUI.SelectionListener, BasicFileChooserUI.UpdateAction, BasicInternalFrameTitlePane.CloseAction,BasicInternalFrameTitlePane.IconifyAction, BasicInternalFrameTitlePane.MaximizeAction, BasicInternalFrameTitlePane.MoveAction, BasicInternalFrameTitlePane.PropertyChangeHandler,BasicInternalFrameTitlePane.RestoreAction, BasicInternalFrameTitlePane.SizeAction, BasicInternalFrameUI.BasicInternalFrameListener, BasicInternalFrameUI.BorderListener,BasicInternalFrameUI.ComponentHandler, BasicInternalFrameUI.GlassPaneDispatcher, BasicInternalFrameUI.InternalFramePropertyChangeListener, BasicLabelUI, BasicListUI.FocusHandler,BasicListUI.ListDataHandler, BasicListUI.ListSelectionHandler, BasicListUI.MouseInputHandler, BasicListUI.PropertyChangeHandler, BasicMenuItemUI.MouseInputHandler, BasicMenuUI.ChangeHandler,BasicMenuUI.MouseInputHandler, BasicOptionPaneUI.ButtonActionListener, BasicOptionPaneUI.PropertyChangeHandler, BasicProgressBarUI.ChangeHandler, BasicRootPaneUI,BasicScrollBarUI.ArrowButtonListener, BasicScrollBarUI.ModelListener, BasicScrollBarUI.PropertyChangeHandler, BasicScrollBarUI.ScrollListener, BasicScrollBarUI.TrackListener,BasicScrollPaneUI.HSBChangeListener, BasicScrollPaneUI.MouseWheelHandler, BasicScrollPaneUI.PropertyChangeHandler, BasicScrollPaneUI.ViewportChangeHandler, BasicScrollPaneUI.VSBChangeListener,BasicSliderUI.ActionScroller, BasicSliderUI.ChangeHandler, BasicSliderUI.ComponentHandler, BasicSliderUI.FocusHandler,
Typical use (Android)“Find” your UI widgets, register listeners on them liteButton = (RadioButton) findViewById(R.id.liteButton); toolButton = (RadioButton) findViewById(R.id.toolButton); numbersButton = (RadioButton) findViewById(R.id.numbersButton);clockView = (MyViewAnim) findViewById(R.id.surfaceView); secondsSwitch = (Switch) findViewById(R.id.showSeconds); dayDateSwitch = (Switch) findViewById(R.id.showDayDate); View.OnClickListener myListener = new View.OnClickListener() { public void onClick(View v) { // fetch state from UI widgets, redraw screen }}; liteButton.setOnClickListener(myListener);toolButton.setOnClickListener(myListener);numbersButton.setOnClickListener(myListener);secondsSwitch.setOnClickListener(myListener);dayDateSwitch.setOnClickListener(myListener);
Find the widget, set its listener.
Typical use (Android)“Find” your UI widgets, register listeners on them liteButton = (RadioButton) findViewById(R.id.liteButton); toolButton = (RadioButton) findViewById(R.id.toolButton); numbersButton = (RadioButton) findViewById(R.id.numbersButton);clockView = (MyViewAnim) findViewById(R.id.surfaceView); secondsSwitch = (Switch) findViewById(R.id.showSeconds); dayDateSwitch = (Switch) findViewById(R.id.showDayDate); View.OnClickListener myListener = new View.OnClickListener() { public void onClick(View v) { // fetch state from UI widgets, redraw screen }}; liteButton.setOnClickListener(myListener);toolButton.setOnClickListener(myListener);numbersButton.setOnClickListener(myListener);secondsSwitch.setOnClickListener(myListener);dayDateSwitch.setOnClickListener(myListener);
Shared “listener” (anon. inner class, Java7’s almost-lambda)
Typical use (Android)“Find” your UI widgets, register listeners on them liteButton = (RadioButton) findViewById(R.id.liteButton); toolButton = (RadioButton) findViewById(R.id.toolButton); numbersButton = (RadioButton) findViewById(R.id.numbersButton);clockView = (MyViewAnim) findViewById(R.id.surfaceView); secondsSwitch = (Switch) findViewById(R.id.showSeconds); dayDateSwitch = (Switch) findViewById(R.id.showDayDate); View.OnClickListener myListener = new View.OnClickListener() { public void onClick(View v) { // fetch state from UI widgets, redraw screen }}; liteButton.setOnClickListener(myListener);toolButton.setOnClickListener(myListener);numbersButton.setOnClickListener(myListener);secondsSwitch.setOnClickListener(myListener);dayDateSwitch.setOnClickListener(myListener);
Closer to home: SparkJava web server
When we see the URL “/hello” or “/hello2”, run the given lambda: public class SimpleServer { public static void main(String[] args) { get("/hello", (request, response) -> "Hello, world"); get("/hello2", (request, response) -> html().with( head().with(title("Hello, world")), body().with( h1("Hello, world"), p("This is some introductory test"))));
Event handler for the “/hello” URL
Closer to home: SparkJava web server
When we see the URL “/hello” or “/hello2”, run the given lambda: public class SimpleServer { public static void main(String[] args) { get("/hello", (request, response) -> "Hello, world"); get("/hello2", (request, response) -> html().with( head().with(title("Hello, world")), body().with( h1("Hello, world"), p("This is some introductory test"))));
In a world with mutation…
Events are used to respond to changes in state
Scoreboard on top Listen to changes in player “health” Listen to click, display remaining time
Players Listen to status of other players (e.g., “if suffering damage, laugh!”)
Why listeners and mutation are awfulDependencies kill you A listens to B, B listens to C, C listens to A? Circular dependencies can lead to infinite recursion! Engineering challenge: maintaining discipline in event dispatches
• What you really want: a “directed acyclic graph” • But there’s no code tool to help you enforce it!
It’s also confusing to debug An event can lead to a cascade of listeners being called Hard to determine causality: why did some callbacks happen?
What about a functional world?There are several general-purpose “functional-reactive programming” (FRP) frameworks like RxJava Here’s an introduction: https://www.bignerdranch.com/blog/what-is-functional-reactive-programming/
We’re not going to go there in Comp215 These frameworks deal with concurrency / multithreading (That’s Comp322 and maybe Comp311)
If you do see FRP code, it looks just like lazy list code Filters, maps, etc. on events that haven’t happened yet. “Subscribe” to events and emit more events. Your Comp215 experience means you’re ready to understand FRP.
How do text adventures work?First, let’s go and play a classic: Zork http://iplayif.com/?story=http%3A%2F%2Fwww.ifarchive.org%2Fif-archive%2Fgames%2Fzcode%2Fzdungeon.z5
What about your adventure game?Example: How might you deal with locked doors?
Keys are functions that update doors from “locked” to “unlocked” The key (or door) might verify that they match properly
Door opening? Another function. Check if the door is unlocked, etc.
Navigating through a door? Another function. Check if there’s a door; if so, run its function. Compose with the function to traverse any exit.
Use monadic composition!
flatmap matchmap
Inputs: Option<state of the world>, desired action
Outputs: Option<state of the world>
More adventure game fun
How might you deal with the pansy of perplexity?
Time-based effects World-state: tracks when the effect starts and ends, updates an “active” or “inactive” field. The “pansy” function is the identity function unless “activated”.
Command-based effects The “pansy” transforms motion comments to different motion commands, or it’s the identity function.
Many different ways to accomplish the same goal!
How do you connect typed commands?> HIT TROLL WITH SWORD
> HIT TROLL WITH FLOWER One of these will be more effective than the other! Special attributes on weapons that normal objects don’t have?
> HIT <target> WITH <item> You need a general-purpose way to map from names to items And then you need a way to read each object’s properties The “hit” verb will query the environment for your <target> and query your inventory for the <item>
Expect to build a bunch of “registries”private static final IMap<RPNTokenPatterns, CalcOp> REGISTRY = TreapMap.of( KeyValue.make(RPNTokenPatterns.PLUS, RPNCalculator::add), KeyValue.make(RPNTokenPatterns.TIMES, RPNCalculator::multiply), KeyValue.make(RPNTokenPatterns.MINUS, RPNCalculator::subtract), KeyValue.make(RPNTokenPatterns.DIVIDE, RPNCalculator::divide), KeyValue.make(RPNTokenPatterns.DUP, RPNCalculator::dup), KeyValue.make(RPNTokenPatterns.DROP, RPNCalculator::drop), KeyValue.make(RPNTokenPatterns.SWAP, RPNCalculator::swap), KeyValue.make(RPNTokenPatterns.EQUALS, RPNCalculator::noop), KeyValue.make(RPNTokenPatterns.FAIL, RPNCalculator::fail), KeyValue.make(RPNTokenPatterns.CLEAR, RPNCalculator::clear));
Or, perhaps, use the JSON functions directly. You can make a choice for how to do this!
You can update a registry as wellprivate static final IMap<RPNTokenPatterns, CalcOp> REGISTRY = TreapMap.of( KeyValue.make(RPNTokenPatterns.PLUS, RPNCalculator::add), KeyValue.make(RPNTokenPatterns.TIMES, RPNCalculator::multiply), KeyValue.make(RPNTokenPatterns.MINUS, RPNCalculator::subtract), KeyValue.make(RPNTokenPatterns.DIVIDE, RPNCalculator::divide), KeyValue.make(RPNTokenPatterns.DUP, RPNCalculator::dup), KeyValue.make(RPNTokenPatterns.DROP, RPNCalculator::drop), KeyValue.make(RPNTokenPatterns.SWAP, RPNCalculator::swap), KeyValue.make(RPNTokenPatterns.EQUALS, RPNCalculator::noop), KeyValue.make(RPNTokenPatterns.FAIL, RPNCalculator::fail), KeyValue.make(RPNTokenPatterns.CLEAR, RPNCalculator::clear));
update() method on a registry, leaves the original alone Perhaps wrap existing functions, perhaps replace them (temporarily?)
Function composition!
flatmap matchmap
This is a universal technique: support non-player characters (e.g., the troll or the thief) area effects (e.g., illumination)
Each transforms the world, returns another world But test them individually!
How should output text work?
How about a field in your world that represents the text output? As each transformer operates, it might append some text. At the end of the turn, you collect all the text and print it.
If one of the world functions has an error? Throw away the output buffer, return a world in the “error” state. Errors should include text about what went wrong
• Not just Option.none ? Don’t advance the state of the world when there’s an error?
How should you begin?This week’s deadline is pretty mellow Load your world (in JSON format) Walk around Look at the rooms
Architect how your world works! What’s your in-memory representation? What’s it mean to move? What’s it mean to look? What’s a room? What’s a door?
Maybe convert JSON to native classes
We talked about this in week7 (edu.rice.week7newspaper):
public interface DB { class Author { public final String email; public final String name; private Author(String email, String name) { this.email = email; this.name = name; }
It’s probably worth it to go with a “native” database for your world.
Another hint: dealing with text input
Don’t bother with scanning and parsing You’re dealing with a very simple command language <Command> ::= <Verb> | <Verb> <Object> | <Verb> …
1) split the input string on whitespace
2) map from <Verb> to a lambda
3) call that lambda with the world and the rest of the input string!
Testing, testing, testing!
Create very simple worlds for your tests One room, one object in the room, etc.
Create very simple functions that operate on those rooms Pick up an object, drop an object, etc.
Unit test them individually
Don’t compose them until they work on their own
But… but… performance!
How many objects and rooms in your world? Hundreds? If each one executes for a million CPU cycles (assuming 1GHz CPU):
• that’s 1 ms / object • Or 100 ms / user command
So your performance basically doesn’t matter! The final project will be another matter entirely
When does performance matter?1) If you’re trying to draw the screen at 60+Hz “Jank is any stuttering, juddering or just plain halting that users see when a site or app isn't keeping up with the refresh rate. Jank is the result of frames taking too long … to make, and it negatively impacts your users and how they experience your site or app.” (more on this coming soon)
2) If you’re running a huge computation Think: Google-scale, where your electricity bills are $millions/year.
3) If you’re running on a tiny computer Think: A pacemaker, where you need surgery to replace a battery.
Q&A
In-class discussion.