P3-SDK 0.9
P3 Software Development Kit
Loading...
Searching...
No Matches
The Mode Layer

Introduction to Mode Programming

As described in the Modes section, the execution of a P3 app is controlled by a group of modes. When modes are created, they're given a priority, and they start running when they're added to the P3's mode queue. Here's an example of creating and starting a mode:

ShotsMode shotsMode = new ShotsMode(p3, Priorities.PRIORITY_MECH);
p3.AddMode(shotsMode);

In this example, ShotsMode is a class (definition not shown) that inherits from Multimorphic.NetProcMachine.Machine.Mode. When a mode is created, the main p3 object and an integer representing its priority are passed into the constructor. In this case, a pre-defined constant Priorities.PRIORITY_MECH contains the priority number. Keeping all priorities defined in a central place makes it easier to manage priorities for all modes.

After being added to the mode queue via the p3.AddMode(shotsMode) call, shotsMode is effectively running. Running means that the mode is active and ready to receive events from the P3. Events can be switch events from the actual P3 machine, timer events, or events sent from other modes or GUI scripts. Refer to the Events in Mode Layer and Events in GUI Layer sections for more information on events.

To stop a mode from running, simply remove it from the mode queue as follows:

p3.RemoveMode(shotsMode)

Note - Common mistakes made with modes include creating them but never starting them and creating them with too low of a priority. Higher priority modes receive events before lower priority modes, and a mode can decide whether or not an event it receives should continue being serviced by lower priority modes.

An Overview of P3 Modes

All P3 apps have an app mode which starts the proceedings (usually by starting the attract mode and a game manager mode) and ends the proceedings (by shutting down the app).

In the interim, the game manager mode will start the primary game mode when appropriate. When the game manager receives an event indicating that the start button has been pressed, it checks the money mode (which is started by the game manager mode when it initializes) to ensure that enough credits are available, then it will start the base game mode (e.g. P3SABaseGameMode).

The base game mode starts the flipper mode and ball launch mode to enable game play. It will also start ball drain mode to catch the end of a ball. The game mode keeps track of the number of balls in play as well as the balls remaining in the game. A separate score mode keeps track of the score.

Modes Included In The Framework

The framework includes frequently required modes. Some examples of common modes are:

Mode Responsibility
BaseGameMode - Derived from BaseAppMode, which instantiates modes used in all apps (e.g. VolumeMode, etc).
- The base mode used in all games.
- Instantiates modes that make up the entire game (e.g. AttractMode, GameManagerMode, GameAttributeManagerMode, etc)
- This is not the base class of all game modes. Base here means Primary, Central or Core mode.
AttractMode - Show ready-for-game state to the user
- Attract art, sound, and lightshow
- Receive notification of Start button press
- Start game mode
GameManagerMode - Starts money mode
- Awaits Start button presses
- Starts game mode
- Awaits game ending
GameAttributeManagerMode - Keeps track of all DataManagerModes, which are modes used to track data (settings, statistics, high scores, etc.)
MoneyMode - Receives events from coin door mechs and attract mode to add/subtract credits
- Reports number of credits available
BallLauncherMode - Launches a ball into play
BallSaveMode - Watches for ball drains within a defined timeframe
- Prevents registration of a ball drain during saver time
- Triggers BallLauncherMode when a ball drains during saver time
BallSearchMode - Watches for absence of switch activity (an indicator of a potential stuck ball situation).
- Activates all mechanisms until switch activity is received.

Sub-classing From Existing Modes

As a developer, you can extend and customize the provided Modes to best suit the specific behavior for your app. It is inevitable that you will sub-class (i.e., extend) the provided mode classes to leverage the methods and fields they define, and amend or augment functionality as needed. For example, every Mode that you will define could be sub-classed from P3Mode:

public class DebugMode : P3Mode

However in many cases, it may be better to sub-class one of the P3Mode subclasses that have been provided (GameMode). For example, a common usage is to define your own subclass of GameMode (<AppCode>GameMode) and then subclass this class to be able to reuse helpful methods that you define along the way.

Events in Mode Layer

Note - This section describes how to send and receive events in Modes. For sending and receiving events in GUI scripts, please refer to Events in GUI Layer.

Receiving Events

Pinball gameplay modes are generally event driven in that gameplay doesn't advance until a specific sequence of events (e.g. switch events or timer events) occurs. Similarly, the modes that make up a pinball application don't advance until they receive events.

The P3 framework supports 4 different types of events in Modes.

  • Switch Events: The result of a physical switch being activated or de-activated. Modes can subscribe to switch events simply by having methods named as follows:
    // Called when swName changes state to active. The parameter will always be a "Switch" object.
    public bool sw_swName_active(Switch sw)
    // Called when swName changes state to inactive
    public bool sw_swName_inactive(Switch sw)
    // Called when swName remains active for 2 seconds. Any value of seconds would work.
    public bool sw_swName_active_for_2s(Switch sw)
    // Called when swName remains active for 250 milliseconds. Any value of milliseconds would work.
    public bool sw_swName_active_for_250ms(Switch sw)
    Modes can also subscribe to switch events programmatically as follows:
    // Subscribe to swName activations immediately (0).
    add_switch_handler(swName, "active", 0, SwitchHandler);
    private bool SwitchHandler(Switch sw)
  • Timer Events: Modes can have specific methods called after specified amounts of time as follows:
    private void SetupTimedCall()
    {
    // Set up a timer called "TimerName" to call the method "TimedCall" after 1.0 seconds.
    delay("TimerName", NetProc.EventType.None, 1.0, new P3.VoidDelegateNoArgs(TimedCall));
    }
    private void TimedCall()
    {
    // Do Something
    }
    private void CancelTimedCall()
    {
    // If you want to abort the timed call, simply cancel the delay event like this:
    cancel_delayed("TimerName");
    }
    Timer calls can optionally have a single parameter (to pass more parameters, create a class to hold them and pass in an instantiated object)
    private void SetupTimedCallWithParam()
    {
    int param = 3;
    // Set up a timer called "TimerName" to call the method "TimedCall" after 1.0 seconds.
    delay("TimerName", NetProc.EventType.None, 1.0, new P3.VoidDelegateOneObjectArg(TimedCall), param);
    }
    private void TimedCall(object param)
    {
    int convertedParam = (int)param;
    // Do Something
    }
  • GUI Events: GUI events are events that are initiated by GUI scripts. They are the mechanism through which the GUI can communicate with and send information to active modes. Once a GUI event handler is added, it remains active for as long as the mode remains active or until the event handler is removed.
    // Subcribe to the GUI event "Evt_GUIEventName" so that GUIEvtHandler is called when the event occurs
    // Note, the GUIEvtHandler will only be called while the mode is active.
    AddGUIEventHandler("Evt_GUIEventName", GUIEvtHandler);
    private void GUIEvtHandler(string evtName, object evtData)
    {
    // Handler call, cast evtData to desired type if you want to use it.
    int data = (int)evtData;
    // Do Something
    }
    // Unsubcribe to the GUI event "Evt_GUIEventName" so that GUIEvtHandler is no longer called when the event occurs.
    // This is typically only used when a mode is still active but shouldn't respond to specific events anymore.
    RemoveGUIEventHandler("Evt_GUIEventName", GUIEvtHandler);
  • Mode Events Mode events are very similar to GUI events, except that they come from other modes. They also include a priority so that higher priority modes are given a chance to respond to the event before lower priority modes. Furthermore, a higher priority mode can choose to stop the propagation of the event to lower modes. Similar to GUI event handlers, mode event handlers remain active while the mode is active, unless the handler is specifically removed before the mode is removed from the mode queue.
    // Subcribe to the Mode event "Evt_ModeEventName" so that EvtHandler is called when the event occurs.
    // Mode events are priority based (highest priority handlers are called first). So send in the mode's Priority when adding the handler.
    // Note - this call can be made at any time. Putting it in the constructor ensures it is always active while the mode is active.
    AddModeEventHandler("Evt_ModeEventName", ModeEvtHandler, Priority);
    private bool ModeEvtHandler(string evtName, object evtData)
    {
    // Handler call, cast evtData to desired type if you want to use it.
    int data = (int)evtData;
    // Do Something
    // Return EVENT_CONTINUE to allow the event to propagate to other (lower priority) handlers or EVENT_STOP to stop propagation here.
    return EVENT_CONTINUE;
    }
    // Unsubcribe to the Mode event "Evt_ModeEventName" so that ModeEvtHandler is no longer called when the event occurs.
    // This is typically only used when a mode is still active but shouldn't respond to specific events anymore.
    RemoveModeEventHandler("Evt_ModeEventName", ModeEvtHandler, Priority);

Sending Mode Events

Events can be sent from a mode to one or more other modes by posting it as follows. Data can be any type of data. Event handlers need to cast the information back to the desired type.

PostModeEventToModes("Evt_EventForModes", data);

Events can be sent from a mode to a GUI script by posting it as follows:

PostModeEventToGUI("Evt_EventForGUI", data);

Note - To send the same event data to both Mode and GUI, two separate Posts are required, one PostModeEventToModes and one PostModeEventToGUI.

Query/Response Events

It is possible to emulate a function returning a value using two events. The first event queries for the value. The target Mode receives that event and reacts by sending a response event. The return value is the event argument of the second event. Since the event handlers of Mode-to-Mode events are executed before PostModeToModes() returns, the return value will be available right after the first Post call. The response event handler is only needed for a short time, it can be removed as soon as the response is received.

This is an example using a real event supported by the framework:

int numWarnings = 0; // a class member variable
AddModeEventHandler("Evt_TiltWarningCountResponse", TiltWarningCountResponseEventHandler, Priority);
PostModeEventToModes("Evt_GetTiltWarnings", null);
// numWarnings already holds the return value here
private bool TiltWarningCountResponseEventHandler(string evtName, object evtData){
numWarnings = (int)evtData;
RemoveModeEventHandler("Evt_TiltWarningCountResponse", TiltWarningCountResponseEventHandler);
return EVENT_STOP;
}

This is an example how the framework might have implemented the "Evt_GetTiltWarnings" event:

int warningCount; // a class member variable
AddModeEventHandler("Evt_GetTiltWarnings", GetTiltWarningsEventHandler, Priority);
private bool GetTiltWarningsEventHandler(string evtName, object evtData)
{
PostModeEventToModes("Evt_TiltWarningCountResponse", warningCount);
return EVENT_STOP;
}

Debugging Events

Event debugging will show which class posts the event and when, and it will show who handles the event. In the case of a switch event or a ModeToMode event, both of which propagate through the priority-based mode queue, the logging information will show all of the modes that handle the event before its propagation is stopped by a mode returning SWITCH_STOP or EVENT_STOP.

// * Switch events *
// The following will add the launch button (Switch name: "launch") to the event logging list.
// Any time the launch button is activated or deactivated, the system will log the sequence of modes that
// are given the chance to process the event.
Multimorphic.NetProcMachine.Logging.Logger.AddSwitchName("launch");
// * Mode to Mode events *
// The following will add mode to mode events to the logging list
Multimorphic.NetProcMachine.EventManager.LogEventName("Evt_MagnetRingCatch", true);
// * Mode to GUI events *
// The following will add mode to GUI events to the logging list
p3.ModesToGUIEventManager.LogEventName("Evt_ShowGUIObject", true);
// * GUI to Mode events *
// The following will add GUI to mode events to the logging list
p3.GUIToModesEventManager.LogEventName("Evt_SceneGoneLive", true);

More mode information: