Skip to content

Commit

Permalink
Modify spinner logic so waveforms are made with loading spinner not p…
Browse files Browse the repository at this point in the history
…rogress bar

Rationalise time parameters for when spinners are shown
Allow spinner payloads that do not use progress bar
Reset scrollbar to default clock range on restarting waveform simulation, make this a constant.
Make all waveform creation spinners button-only not progress bar.

fix progress bar
  • Loading branch information
tomcl committed Sep 9, 2024
1 parent 20d38a3 commit deff960
Show file tree
Hide file tree
Showing 7 changed files with 72 additions and 44 deletions.
16 changes: 12 additions & 4 deletions src/Renderer/Model/ModelType.fs
Original file line number Diff line number Diff line change
Expand Up @@ -540,12 +540,16 @@ type UserData = {
Theme: DrawModelType.SymbolT.ThemeType
}

type SpinnerState =
| WaveSimSpinner


type SpinPayload = {
/// if false do not show progress bat screen, but still show spinner in button.
UseProgressBar: bool
/// text displayed with progress bar
Name: string
/// ToDo / Total = progress bar level
ToDo: int
/// ToDo / Total = progress bar level
Total: int
}

Expand Down Expand Up @@ -578,10 +582,14 @@ let sortType_ = Lens.create (fun a -> a.SortType) (fun s a -> {a with SortType =
let algebraIns_ = Lens.create (fun a -> a.AlgebraIns) (fun s a -> {a with AlgebraIns = s})
let gridCache_ = Lens.create (fun a -> a.GridCache) (fun s a -> {a with GridCache = s})

type RunData = {
ButtonSpinnerOn: bool
FnToRun: ((Msg -> unit) -> (Model -> Model))
}

type Model = {
/// Function to be run after rendering to update the model
RunAfterRender: ((Msg -> unit) -> (Model -> Model)) option
RunAfterRenderWithSpinner: RunData option
/// User data for the application
UserData: UserData
/// Map of sheet name to WaveSimModel
Expand Down Expand Up @@ -671,7 +679,7 @@ type Model = {
let waveSimSheet_ = Lens.create (fun a -> a.WaveSimSheet) (fun s a -> {a with WaveSimSheet = s})
let waveSim_ = Lens.create (fun a -> a.WaveSim) (fun s a -> {a with WaveSim = s})

let runAfterRender_ = Lens.create (fun a -> a.RunAfterRender) (fun s a -> {a with RunAfterRender = s})
let runAfterRender_ = Lens.create (fun a -> a.RunAfterRenderWithSpinner) (fun s a -> {a with RunAfterRenderWithSpinner = s})
let rightPaneTabVisible_ = Lens.create (fun a -> a.RightPaneTabVisible) (fun s a -> {a with RightPaneTabVisible = s})
let simSubTabVisible_ = Lens.create (fun a -> a.SimSubTabVisible) (fun s a -> {a with SimSubTabVisible = s})
let buildVisible_ = Lens.create (fun a -> a.BuildVisible) (fun s a -> {a with BuildVisible = s})
Expand Down
8 changes: 4 additions & 4 deletions src/Renderer/UI/MainView.fs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ let viewOnDiagramButtons model dispatch =


let init() = {
RunAfterRender = None
RunAfterRenderWithSpinner = None
SpinnerPayload = None
Spinner = None
UISheetTrail = []
Expand Down Expand Up @@ -376,11 +376,11 @@ let displayView model dispatch =
ScrollbarMouseMsg (event.clientX, ClearScrollbarDrag, dispatch) |> dispatch

let afterRenderHook: IHTMLProp list =
match model.RunAfterRender with
| Some fn ->
match model.RunAfterRenderWithSpinner with
| Some {FnToRun=fn} ->
[Ref (fun element ->
if element <> null then
dispatch <| DispatchDelayed (0, UpdateModel ((fun model -> { model with RunAfterRender = None }) >> fn dispatch)))]
dispatch <| DispatchDelayed (0, UpdateModel ((fun model -> { model with RunAfterRenderWithSpinner = None }) >> fn dispatch)))]
| None -> []

match model.Spinner with
Expand Down
6 changes: 5 additions & 1 deletion src/Renderer/UI/ModelHelpers.fs
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,14 @@ module Constants =
let maxWarnSimulationSize = 100000
let maxSimulationSize = 4000000
let minScrollingWindow = 200

let wsButtonHeight = 30
let wsButtonWidth = 120
let wsButtonFontSize = 16

/// initial number of clock cycles navigated by the scrollbar.
let scrollbarBkgRepCyclesInit = 100

/// type used for CSS grids in the UI to position an item on a grid
type CSSGridPos =
| PosElement of int * int
Expand Down Expand Up @@ -70,7 +74,7 @@ let initWSModel : WaveSimModel = {
ScrollbarTbPos = 0.0 // overwritten when first rendered
ScrollbarTbOffset = None // default value: not in scroll
ScrollbarBkgWidth = 0.0 // overwritten when first rendered
ScrollbarBkgRepCycs = 100 // default value
ScrollbarBkgRepCycs = Constants.scrollbarBkgRepCyclesInit // default value
ScrollbarQueueIsEmpty = true // default value: empty scroll queue
}

Expand Down
8 changes: 4 additions & 4 deletions src/Renderer/UI/UIPopups.fs
Original file line number Diff line number Diff line change
Expand Up @@ -477,13 +477,13 @@ let makePopupButton (title: string) (menu: Model -> ReactElement) (buttonLegend:
/// Called from the view function
let viewPopup model dispatch =
match model.PopupDialogData.Progress, model.PopupViewFunc, model.SpinnerPayload with
| None, None, None ->
div [] []
| _, _, Some payload ->
| _, _, Some ({UseProgressBar=true} as payload) ->
viewSpinnerPopup payload model dispatch
| Some amount, _, _ ->
progressPopup simulationLegend model dispatch
| None, Some popup, _ -> popup dispatch model
| None, Some popup, _ -> popup dispatch model
| _ -> div [] []




Expand Down
2 changes: 1 addition & 1 deletion src/Renderer/UI/Update.fs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ let update (msg : Msg) oldModel =

match testMsg with
| RunAfterRender( dispatch, fn) ->
{model with RunAfterRender = Some fn}, Cmd.none
{model with RunAfterRenderWithSpinner = Some {FnToRun=fn; ButtonSpinnerOn = true}}, Cmd.none

| ChangeWaveSimMultiplier key ->
let table = Constants.multipliers
Expand Down
13 changes: 9 additions & 4 deletions src/Renderer/UI/WaveSim/WaveSimStyle.fs
Original file line number Diff line number Diff line change
Expand Up @@ -70,20 +70,25 @@ module Constants =
let softScrollBarWidth: float = 25.0

/// <summary>Minimum width of the scrollbar thumb, in pixels.</summary>
let scrollbarThumbMinWidth: float = 5.0
let scrollbarThumbMinWidth: float = 10.0

/// height of the top half of the wave sim window (including tabs) when waveforms are displayed
let topHalfHeight = 260.

// helpers constants
/// initial time running simulation without spinner to check speed (in ms)
let initSimulationTime = 100.
/// max estimated time to run simulation and not need a spinner (in ms)
let maxSimulationTimeWithoutSpinner = 300.


/// initial time making waveforms without spinner to check speed (in ms)
let initWaveformTime = 100.
let initWaveformTime = 50.
/// max estimated time to generate new waveforms and not need a spinner (in ms)
let maxWaveCreationTimeWithoutSpinner = 100.



/// max estimated time to run simulation and not need a spinner (in ms)
let maxSimulationTimeWithoutSpinner = 300.
/// The horizontal length of a transition cross-hatch for non-binary waveforms
let nonBinaryTransLen : float = 2.

Expand Down
63 changes: 37 additions & 26 deletions src/Renderer/UI/WaveSim/WaveSimTop.fs
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,17 @@ open Optics
open Optics.Operators

/// Start or update a spinner popup
let updateSpinner (name:string) payload (numToDo:int) (model: Model) =
let setProgressBar (name:string) payload (numToDo:int) (model: Model) =
match model.SpinnerPayload with
| Some sp when sp.Name = name ->
{model with SpinnerPayload = Some {Name = name; ToDo = numToDo; Total = sp.Total}}
| _ ->
{model with SpinnerPayload = Some {Name = name; ToDo = numToDo; Total = numToDo + 1}}
|> (fun model -> {model with RunAfterRender = Some payload})
| Some sp when sp.Name = name -> // continuation of an existing progress bar
{model with SpinnerPayload = Some {sp with ToDo = numToDo}}
| _ -> // A new progress bar is needed
{model with SpinnerPayload = Some {UseProgressBar = true; Name = name; ToDo = numToDo; Total = numToDo + 1}}
|> (fun model -> {model with RunAfterRenderWithSpinner = Some {FnToRun=payload; ButtonSpinnerOn=true}})

let setButtonSpinner payload (model: Model) =
{model with SpinnerPayload = Some {UseProgressBar = false; Name = ""; ToDo = 0; Total = 0}}
|> (fun model -> {model with RunAfterRenderWithSpinner = Some {FnToRun = payload; ButtonSpinnerOn = true}})

/// remove the spinner popup
let cancelSpinner (model:Model) =
Expand All @@ -49,7 +53,6 @@ let cancelSpinner (model:Model) =
/// 2. Remake the Wave headers, one for each selected waveform.
/// 3. Remake (or make for first time) the saved waveform SVGs for all selected waveforms.
let rec refreshWaveSim (newSimulation: bool) (wsModel: WaveSimModel) (model: Model): Model * Elmish.Cmd<Msg> =

// The function performs immediately the first part of the main long functions to determine their time and as needed splits
// the rest of the work into multiple function calls using a spinner to alert the user to the delay.
// The Spinner (in reality a progress bar) is used if the estimated time to completion is longer than
Expand All @@ -65,11 +68,10 @@ let rec refreshWaveSim (newSimulation: bool) (wsModel: WaveSimModel) (model: Mod
/// that otherwise remove focus.
let dispatchFocusAfterRender model =
let focusCurrClk1 _ model =
printfn "focussed!"//>
let el = Browser.Dom.document.getElementById "clkCycleInput"
if el <> null then el.focus()
model
{ model with RunAfterRender = Some <| Option.defaultValue focusCurrClk1 model.RunAfterRender}
{ model with RunAfterRenderWithSpinner = Some <| Option.defaultValue {FnToRun=focusCurrClk1;ButtonSpinnerOn=false} model.RunAfterRenderWithSpinner }


/// Make sure we always have consistent parameters. They will be written back to model after this function terminates.
Expand All @@ -84,14 +86,12 @@ let rec refreshWaveSim (newSimulation: bool) (wsModel: WaveSimModel) (model: Mod
// Use the given (more uptodate) wsModel. This ensures it is returned from this function.
let model = updateWSModel (fun _ -> wsModel) model

// Start timing - used in this function to decide whether a callback is needed to continue the work
let start = TimeHelpers.getTimeMs ()

// local containing the current fast simulation to be examined and extended if need be.
let fs = Simulator.getFastSim()

/// This is the highest simulation cycle that might be required in this simulation
/// as determined by the current WSConfig.
/// as determined by the current WSConfig. The limit for this refresh will be the minimum
/// of this and what is required for the current view.
let cycleLimit =
(wsModel.ShownCycles + wsModel.StartCycle)*wsModel.SamplingZoom //last shown cycle + 1, to get transitions
|> min (wsModel.WSConfig.LastClock + Constants.maxStepsOverflow - 1 + wsModel.SamplingZoom) // cannot go beyond the array
Expand All @@ -110,31 +110,29 @@ let rec refreshWaveSim (newSimulation: bool) (wsModel: WaveSimModel) (model: Mod
// length is therefore limited to the size of the buffer.
// All date from time = 0 is stored.


/// This function calculates the last cycle needed for the simulation for the current view.
let lastCycleNeeded wsModel =
(wsModel.ShownCycles + wsModel.StartCycle)*wsModel.SamplingZoom + 1
|> min cycleLimit
/// This function is called when the simulation is running and the spinner is needed.
/// It dispatches a continuation which will recursively call refreshWaveSim
let runSimulationWithSpinner cyclesToDo model =
printfn "dispatching continuation..."
let spinnerFunc = fun _dispatch model ->
let wsModel = getWSModel model
fst (refreshWaveSim false wsModel model) // get model after refreshWaveSim has run
let model =
model
|> updateSpinner "Extending Circuit Simulation..." spinnerFunc cyclesToDo
|> setProgressBar $"Extending Circuit Simulation..." spinnerFunc cyclesToDo
model, Elmish.Cmd.none


FastRun.runFastSimulation (Some Constants.initSimulationTime) (lastCycleNeeded wsModel) fs
|> (fun speedOpt -> // if not None the fast simulation has timed out and has not yet completed
let cyclesToDo = (lastCycleNeeded wsModel) - fs.ClockTick // may be negative

match speedOpt with
| Some speed when float cyclesToDo / speed + Constants.initSimulationTime > Constants.maxSimulationTimeWithoutSpinner ->
// The simulation is taking too long. We need to use a spinner.
runSimulationWithSpinner cyclesToDo model // A callback to refreshWaveSim is made didpatched from this function
runSimulationWithSpinner cyclesToDo model // A callback to refreshWaveSim is made dispatched from this function
| _ ->
// Force simulation to finish now in case it is not finished.
// We know this will be quick enough not to need a spinner.
Expand All @@ -145,8 +143,14 @@ let rec refreshWaveSim (newSimulation: bool) (wsModel: WaveSimModel) (model: Mod
// That decision is made below with the help of makeWaveformsWithTimeOut.

// Validate and update all parameters affecting waveforms.
let model = updateViewerWidthInWaveSim model.WaveSimViewerWidth model
let wsModel = getWSModel model
let model =
printfn "Cancelling spinner"
updateViewerWidthInWaveSim model.WaveSimViewerWidth model
// cancel any spinner so that when a new one is started
// it will have teh correct total number of steps to do.
//|> (fun model -> {model with SpinnerPayload = None})
let wsModel =
getWSModel model

// redo waves based on simulation data which is now correct
let allWaves = wsModel.AllWaves
Expand All @@ -156,6 +160,7 @@ let rec refreshWaveSim (newSimulation: bool) (wsModel: WaveSimModel) (model: Mod
// The simulation has changed due to viewer width change. We need to redo the simulation.
// This is done by calling refreshWaveSim again, we will come back here
// after the simulation has been redone and is uptodate.
// TODO: maybe the viewer width check should be earlier in this function?
refreshWaveSim newSimulation wsModel model
else
// need to use isSameWave here because array index may have changed
Expand All @@ -173,12 +178,12 @@ let rec refreshWaveSim (newSimulation: bool) (wsModel: WaveSimModel) (model: Mod
failwithf $"Sanity check failed: wsModel.StartCycle = {wsModel.StartCycle}"
let spinnerInfo =
let numToDo = wavesToBeMade.Length
WaveSimSVGs.makeWaveformsWithTimeOut (Some Constants.initSimulationTime) wsModel wavesToBeMade
WaveSimSVGs.makeWaveformsWithTimeOut (Some <| Constants.initWaveformTime ) wsModel wavesToBeMade
|> (fun res ->
match wavesToBeMade.Length - res.NumberDone, res.TimeTaken with
| _, None | 0, _->
{| WSM=res.WSM; SpinnerPayload=None; NumToDo=numToDo|} // finished
| numToDo, Some t when float numToDo * t / float res.NumberDone < Constants.maxSimulationTimeWithoutSpinner ->
| numToDo, Some t when float numToDo * t / float res.NumberDone < Constants.maxWaveCreationTimeWithoutSpinner ->
let res2 = WaveSimSVGs.makeWaveformsWithTimeOut None res.WSM wavesToBeMade
{| WSM= res2.WSM; SpinnerPayload=None; NumToDo = numToDo - res2.NumberDone|}
| numToDo, _ ->
Expand Down Expand Up @@ -225,7 +230,7 @@ let rec refreshWaveSim (newSimulation: bool) (wsModel: WaveSimModel) (model: Mod
cancelSpinner model
|> dispatchFocusAfterRender
| Some (spinnerName, spinnerAction) ->
updateSpinner spinnerName (fun _dispatch model -> spinnerAction model) spinnerInfo.NumToDo model
setButtonSpinner (fun _dispatch model -> spinnerAction model) model
|> updateWSModel (fun _ -> {ws with DefaultCursor = Default})
|> (fun model -> model, Elmish.Cmd.none))

Expand Down Expand Up @@ -255,7 +260,9 @@ let refreshButtonAction canvasState model dispatch = fun _ ->
model
|> removeAllSimulationsFromModel
|> fun model -> {model with WaveSimSheet = Some wsSheet}
let wsModel = getWSModel model
let wsModel =
getWSModel model
|> fun wsModel -> {wsModel with ScrollbarBkgRepCycs= Constants.scrollbarBkgRepCyclesInit}
//printfn $"simSheet={wsSheet}, wsModel sheet = {wsModel.TopSheet},{wsModel.FastSim.SimulatedTopSheet}, state={wsModel.State}"
let simRes =
// Here is where the new fast simulation is created
Expand Down Expand Up @@ -333,7 +340,11 @@ let topHalf canvasState (model: Model) dispatch : ReactElement * bool =
refreshButtonAction canvasState model dispatch
let waveEnd model = endButtonAction canvasState model dispatch
let wbo = getWaveSimButtonOptions canvasState model wsModel
let isLoading = Option.isSome model.RunAfterRender
let isLoading =
match model.RunAfterRenderWithSpinner, model.SpinnerPayload with
| Some {ButtonSpinnerOn = true}, _ -> true
| _ , Some _ -> true
| _ -> false
let startEndButton =
button
(topHalfButtonPropsLoading isLoading wbo.StartEndColor "startEndButton" false)
Expand Down Expand Up @@ -452,7 +463,7 @@ let viewWaveSim canvasState (model: Model) dispatch : ReactElement =
div [ viewWaveSimStyle ]
[
top
if needsBottomHalf && Option.isNone model.SpinnerPayload then bottomHalf() else div [] []
if needsBottomHalf && (match model.SpinnerPayload with | Some {UseProgressBar=true } -> false | _ -> true) then bottomHalf() else div [] []
]

]
Expand Down

0 comments on commit deff960

Please sign in to comment.