MCM State Options - schlangster/skyui GitHub Wiki

Introduction

State options have been added in MCM 2.0 as a better method for organizing option event handler code.

However, due to the fact that mod authors are already used to the classic API, or made complex menus that would take a long time to convert, state options will not replace the default API. But if you are interested in writing better code, or if you're facing scalability issues, consider using them.

The rest of this document assumes that you are already familiar with the basic MCM principles.

Motivation

As explained in the other guides, the original method of identifying single options is by option ID. A generic example:

int aOID
int bOID
...
int zOID

event OnPageReset(string page)
	aOID = AddToggleOption("A", true)
	bOID = AddToggleOption("B", true)
	...
	zOID = AddToggleOption("Z", true)
endEvent

event OnOptionSelect(int option)
	if (option == aOID)
		SetTextOptionValue(aOID, false)
	elseIf (option == bOID)
	...
	elseIf (option == zOID)
		SetTextOptionValue(zOID, false)
	endIf
endEvent

event OnOptionDefault(int option)
	if (option == aOID)
		SetTextOptionValue(aOID, true)
	elseIf (option == bOID)
	...
	elseIf (option == zOID)
		SetTextOptionValue(zOID, false)
	endIf
endEvent

event OnOptionHighlight(int option)
	if (option == aOID)
		SetInfoText("Option A bla")
	elseIf (option == bOID)
	...
	elseIf (option == zOID)
		SetInfoText("Option Z bla")
	endIf
endEvent

The actual handler code is first grouped by event type, then a cascade of if statements selects it by option ID. While this clearly works, there are several issues with this approach:

  • The handlers responsible for a single option are spread across the whole script. As the number of options grows, this becomes increasingly difficult to manage. For example, if you want to change an option, you'll have to find and modify code at many separate locations.
  • You might also argue that as the number of option grows, it has to execute a lot of unnecessary checks, though performance is not the primary concern here.
  • Changes are rarely limited to a single location in the script, which makes it easier to introduce bugs and harder to find them later.

With state options, we resolve these issues, because they allow to encapsulate the code responsible for an option in a single state.

Using State Options

Based on the earlier example, this is how the state option approach looks like:

event OnPageReset(string page)
	AddToggleOptionST("OPTION_A", "A", true)
	AddToggleOptionST("OPTION_B", "B", true)
	...
	AddToggleOptionST("OPTION_Z", "Z", true)
endEvent

state OPTION_A
	event OnSelectST()
		SetTextOptionValueST(true)
	endEvent

	event OnDefaultST()
		SetTextOptionValueST(false)
	endEvent

	event OnHighlightST()
		SetInfoText("Option A bla")
	endEvent
endState

state OPTION_B
...

state OPTION_Z
	event OnSelectST()
		SetTextOptionValueST(true)
	endEvent

	event OnDefaultST()
		SetTextOptionValueST(false)
	endEvent

	event OnHighlightST()
		SetInfoText("Option Z bla")
	endEvent
endState

Let's have a look at the most obvious changes:

  • There's no more need for option IDs to be stored in variables. Instead, options are now uniquely named by the state, which contains their handlers.
  • There are separate small handlers like OnSelect or OnDefault for each option, instead of a single big one to handle all possible OnSelect events based on the passed option ID etc.

To understand how that fits in with the old API, let's summarize how options were managed previously:

  • (1) They were added with the Add*Option functions in OnPageReset.
  • (2) Option-specific events were handled in event OnOption*(int option, ...).
  • (3) Set*OptionValue(int option, ...) and SetOptionFlags(int option, ...) were used to change option data later.

These functions and events are now replaced by their state option equivalents (note the ST suffix):

  • (1) Add*OptionST(string stateName, ...) replaces Add*Option.
  • (2) Multiple OnSelectST handlers in their respective states replace OnOptionSelect. The same happens for other option ID based events.
  • (3) Set*OptionValueST replaces Set*OptionValue(int option, ...), SetOptionFlagsST replaces SetOptionFlags(int option, ...).

Any non-option specific events remain the same, i.e. OnPageReset or OnConfigInit. Note that you can use both state options and regular options in the same, but it's not recommended.

Example

Here's an example, showing the state option usage for all possible option types:

; State variables
bool toggleVal = false
string textVal = "NOT PRESSED"
int sliderVal = 50
int menuIdx = 0
int colorVal = 0xFFFFFF
int keyCode = 10

; ...

event OnPageReset(string page)
	; Those two don't need *ST variants, because they never returned option IDs anyway
	AddHeaderOption("Options")
	AddEmptyOption()

	AddToggleOptionST("MY_TOGGLE", "Toggle option", toggleVal)
	AddTextOptionST("MY_TEXT", "Text option", textVal)
	AddSliderOptionST("MY_SLIDER", "Slider option", sliderVal)
	AddMenuOptionST("MY_MENU", "Menu option", menuEntries[menuIdx])
	AddColorOptionST("MY_COLOR", "Color option", colorVal)
	AddKeyMapOptionST("MY_KEYMAP", "Keymap option", keyCode)

	AddEmptyOption()

	AddTextOptionST("KILL_SWITCH", "DO NOT PRESS THIS BUTTON", "")
endEvent

state MY_TOGGLE ; TOGGLE
	event OnSelectST()
		toggleVal = !toggleVal
		SetToggleOptionValueST(toggleVal)
	endEvent

	event OnDefaultST()
		toggleVal = false
		SetToggleOptionValueST(toggleVal)
	endEvent

	event OnHighlightST()
		SetInfoText("A toggle option")
	endEvent
endState

state MY_TEXT ; TEXT
	event OnSelectST()
		textVal = "PRESSED"
		SetTextOptionValueST(textVal)
	endEvent

	event OnDefaultST()
		textVal = "NOT PRESSED"
		SetTextOptionValueST(textVal)
	endEvent

	event OnHighlightST()
		SetInfoText("A text option")
	endEvent
endState

state MY_SLIDER ; SLIDER
	event OnSliderOpenST()
		SetSliderDialogStartValue(sliderVal)
		SetSliderDialogDefaultValue(50)
		SetSliderDialogRange(0, 100)
		SetSliderDialogInterval(1)
	endEvent

	event OnSliderAcceptST(float value)
		sliderVal = value as int
		SetSliderOptionValueST(sliderVal )
	endEvent

	event OnDefaultST()
		sliderVal = 50
		SetSliderOptionValueST(sliderVal )
	endEvent

	event OnHighlightST()
		SetInfoText("A slider option")
	endEvent
endState

state MY_MENU ; MENU
	event OnMenuOpenST()
		SetMenuDialogStartIndex(menuIdx)
		SetMenuDialogDefaultIndex(0)
		SetMenuDialogOptions(menuEntries)
	endEvent

	event OnMenuAcceptST(int index)
		menuIdx = index
		SetMenuOptionValueST(menuEntries[menuIdx])
	endEvent

	event OnDefaultST()
		menuIdx = 0
		SetMenuOptionValueST(menuEntries[menuIdx])
	endEvent

	event OnHighlightST()
		SetInfoText("A menu option")
	endEvent
endState

state MY_COLOR ; COLOR
	event OnColorOpenST()
		SetColorDialogStartColor(colorVal)
		SetColorDialogDefaultColor(0xFFFFFF)
	endEvent

	event OnColorAcceptST(int color)
		colorVal = index
		SetColorOptionValueST(colorVal)
	endEvent

	event OnDefaultST()
		colorVal = 0xFFFFFF
		SetColorOptionValueST(colorVal)
	endEvent

	event OnHighlightST()
		SetInfoText("A color option")
	endEvent
endState

state MY_KEYMAP ; KEYMAP
	event OnKeyMapChangeST(int newKeyCode, string conflictControl, string conflictName)
		keyCode = newKeyCode
		SetKeyMapOptionValueST(keyCode)
	endEvent

	event OnDefaultST()
		keyCode = 10
		SetKeyMapOptionValueST(keyCode)
	endEvent

	event OnHighlightST()
		SetInfoText("A keymap option")
	endEvent
endState

state KILL_SWITCH ; TEXT
	event OnSelectST()
		SetTextOptionValueST("why...?", true, "MY_TEXT")
		SetOptionFlagsST(OPTION_FLAG_DISABLED, true, "MY_TEXT")
		SetOptionFlagsST(OPTION_FLAG_DISABLED, true, "MY_TOGGLE")
		SetOptionFlagsST(OPTION_FLAG_DISABLED, true, "MY_SLIDER")
		SetOptionFlagsST(OPTION_FLAG_DISABLED, true, "MY_MENU")
		SetOptionFlagsST(OPTION_FLAG_DISABLED, true, "MY_COLOR")
		SetOptionFlagsST(OPTION_FLAG_DISABLED, true, "MY_KEYMAP")
		SetOptionFlagsST(OPTION_FLAG_DISABLED, true, "KILL_SWITCH")
		SetTextOptionValueST("BOOM")
	endEvent
endState

For a full script utilizing state options, have a look at the current SKI_ConfigMenu.