Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Input state management #25

Open
9 tasks
antw opened this issue Jun 21, 2021 · 0 comments
Open
9 tasks

Input state management #25

antw opened this issue Jun 21, 2021 · 0 comments
Labels
rfc A design spec and request for comments

Comments

@antw
Copy link
Contributor

antw commented Jun 21, 2021

The ETM interface needs to fetch the initial inputs state for a scenario when the scenario loads, and keep track of when the user makes changes.

Making changes prompts a new request to ETEngine to apply the new inputs, and fetch new versions of any active gqueries (see #24 for gquery state management).

Tasks

Basic behaviour

  • Fetch initial state from ETEngine when the scenario loads.
  • Create a reusable model or helper functions for interacting with the input.
  • Add support for enum inputs, whose value must be one of the permitted_values provided by ETEngine.
  • Add support for boolean inputs, whose value must be true or false.
  • Add support for numeric inputs (the default), whose value must be between the min and max.
  • Account for when ETEngine responds with a list of inputs, but one or more inputs indicate an error occurred (for example, when a min/max value couldn't be calculated due to bad GQL).

When storing a new value

  • Trigger an update request to ETEngine.

Balancing values

  • Create a balancer
  • Use balancer when updating input values

Types

We should have separate types inputs: numerical, boolean, and enum. This will allow us to restrict input element components (slider, toggle, radio) to only compatible input types:

type Input = {
  key: string;
  // etc
}

type NumericInput = Input & {
  min: number;
  max: number;
  type: 'number';
  unit: string;
  value: number;
}

type EnumInput = Input & {
  allowedValues: string[];
  type: 'enum';
  value: string;
};

type BooleanInput = Input & {
  type: 'boolean';
  value: boolean;
};

// etc

Components

There are many ways an input value can be represented, with the most commonly used being Slider. However, with global state management, even Slider can be split into sub-components each of which represent and modify the input state:

  • (Basic)Slider shows the value on a horizontal scale, allowing the user to move a handle.
  • ResetButton is disabled when the current input value is equal to the default value.
  • DecreaseButton is disabled when the current input value is equal to the minimum value. Can be clicked to reduce the value.
  • IncreaseButton is disabled when the current input value is equal to the maximum value. Can be clicked to increase the value.
  • InputValue is a simple <output> showing the current formatted value.
  • ValueSelector is a pop-up which allows the user to write a custom value.

Alternative components can include radio groups for enum inputs and toggle switches for booleans.

Cancelling requests

When a user changes several inputs in quick succession, the current ETModel sends a new request for each change with the results being applied to the UI in the order they are received.

                               --- Time -->

* First change
# Request one ---------------------> # Response applied

                * Second change
                # Request two ---------------------> # Response applied

		                * Third change
                                # Request three ---------------------> # Response applied

This leads to the UI changing many times, and unnecessary requests being sent.

There are two alternatives:

  • ETFlex-style queueing: if a request is already in-flight, wait until a response is received before sending a second request. Batch multiple slider updates together.

    * First change
    # Request one ---------------------> # Response applied
    
                    * Second change (queued)
    		                * Third change (queued)
                        
                                    # Request three ---------------------> # Response applied
                                      Contains second and
                                      third change
    
  • Abort in-progress requests: It may be possible to abort in-progress requests using AbortController. The viability of this approach likely depends on whether aborting the request stops ETEngine from processing it further (freeing up resources for other requests).

    * First change
    # Request one --> # Cancelled
    
                      * Second change
                      # Request two --> # Cancelled
    
        	                            * Third change
                                        # Request three ---------------------> # Response applied
    

Input balancing

Some inputs belong to a "share group". These inputs represent percentages, where their combined values sum to 100. If the user reduces a slider in a group from 10% to 5%, some other slider in the group needs to increase by 5% to ensure their sum remains 100.

In the current ETM, this is done with Balancer. When a slider is changed, the balancer receives the slider and the new value, and attempts to change the other slider values (the "subordinates").

Balancer has two strategies for balancing a group:

  1. Change the value of the first subordinate until either the group balances, or it has reached its min or max. If the group is not yet balanced, move on to the next subordinate.
  2. If the first strategy fails, it means the new slider value cannot be set while also ensuring the group sums to 100. In this case balanceToClosestEquilibrium performs a binary search for the value closest to what the user entered, while also ensuring the group balances.

Balancer acts as part of the view layer in the current ETM, operating on instances of Quinn (the slider UI). The new balancer should be part of the state management for inputs – and not part of the view – since this will allow us to set values for inputs even when a slider or other UI element hasn't been rendered.

For example, in Redux:

dispatch({
  type: 'SET_INPUT',
  key: 'my_input',
  value: 10
})

... should trigger balancing of the group. This action would receive the input key and value, create a balancer for the group, and set all values for the group simultaneously in one state update.

quintel/osmosis (the ETEngine input balancer) may serve as inspiration.

@antw antw added the rfc A design spec and request for comments label Jun 21, 2021
@antw antw added this to the Phase two milestone Jun 21, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
rfc A design spec and request for comments
Projects
None yet
Development

No branches or pull requests

1 participant