diff --git a/egui/loopctrl.go b/egui/loopctrl.go index 950e6878..07d3455e 100644 --- a/egui/loopctrl.go +++ b/egui/loopctrl.go @@ -5,7 +5,11 @@ package egui import ( + "cmp" + "slices" + "cogentcore.org/core/core" + "cogentcore.org/core/enums" "cogentcore.org/core/events" "cogentcore.org/core/icons" "cogentcore.org/core/styles" @@ -15,16 +19,72 @@ import ( "github.com/emer/emergent/v2/looper" ) -// AddLooperCtrl adds toolbar control for looper.Stack -// with Run, Step controls. -func (gui *GUI) AddLooperCtrl(p *tree.Plan, loops *looper.Manager, modes []etime.Modes, prefix ...string) { - pfx := "" - if len(prefix) == 1 { - pfx = prefix[0] + ": " +// AddLooperCtrl adds toolbar control for looper.Stacks with Init, Run, Step controls, +// with selector for which stack is being controlled. +func (gui *GUI) AddLooperCtrl(p *tree.Plan, loops *looper.Stacks) { + modes := make([]enums.Enum, len(loops.Stacks)) + var stepChoose *core.Chooser + var stepNSpin *core.Spinner + i := 0 + for m := range loops.Stacks { + modes[i] = m + i++ + } + slices.SortFunc(modes, func(a, b enums.Enum) int { + return cmp.Compare(a.Int64(), b.Int64()) + }) + curMode := modes[0] + curStep := loops.Stacks[curMode].StepLevel + + updateSteps := func() { + st := loops.Stacks[curMode] + stepStrs := make([]string, len(st.Order)) + cur := "" + for i, s := range st.Order { + sv := s.String() + stepStrs[i] = sv + if s.Int64() == curStep.Int64() { + cur = sv + } + } + stepChoose.SetStrings(stepStrs...) + stepChoose.SetCurrentValue(cur) } - gui.AddToolbarItem(p, ToolbarItem{Label: pfx + "Stop", + + tree.AddAt(p, "loop-mode", func(w *core.Switches) { + w.SetType(core.SwitchSegmentedButton) + w.Mutex = true + w.SetEnums(modes...) + w.SelectValue(curMode) + w.FinalStyler(func(s *styles.Style) { + s.Grow.Set(0, 0) + }) + w.OnChange(func(e events.Event) { + curMode = w.SelectedItem().Value.(enums.Enum) + st := loops.Stacks[curMode] + if st != nil { + curStep = st.StepLevel + } + updateSteps() + stepChoose.Update() + stepN := st.Loops[curStep].StepCount + stepNSpin.SetValue(float32(stepN)) + stepNSpin.Update() + }) + }) + + gui.AddToolbarItem(p, ToolbarItem{Label: "Init", + Icon: icons.Update, + Tooltip: "Initializes running and state for current mode.", + Active: ActiveStopped, + Func: func() { + loops.InitMode(curMode) + }, + }) + + gui.AddToolbarItem(p, ToolbarItem{Label: "Stop", Icon: icons.Stop, - Tooltip: "Interrupts running. running / stepping picks back up where it left off.", + Tooltip: "Interrupts current running. Will pick back up where it left off.", Active: ActiveRunning, Func: func() { loops.Stop(etime.Cycle) @@ -33,80 +93,74 @@ func (gui *GUI) AddLooperCtrl(p *tree.Plan, loops *looper.Manager, modes []etime }, }) - for _, m := range modes { - mode := m - tree.AddAt(p, pfx+mode.String()+"-run", func(w *core.Button) { - tb := gui.Toolbar - w.SetText(pfx + mode.String() + " Run").SetIcon(icons.PlayArrow). - SetTooltip("Run the " + pfx + mode.String() + " process") - w.FirstStyler(func(s *styles.Style) { s.SetEnabled(!gui.IsRunning) }) - w.OnClick(func(e events.Event) { - if !gui.IsRunning { - gui.IsRunning = true - tb.Restyle() - go func() { - loops.Run(mode) - gui.Stopped() - }() - } - }) + tree.AddAt(p, "loop-run", func(w *core.Button) { + tb := gui.Toolbar + w.SetText("Run").SetIcon(icons.PlayArrow). + SetTooltip("Run the current mode, picking up from where it left off last time (Init to restart)") + w.FirstStyler(func(s *styles.Style) { s.SetEnabled(!gui.IsRunning) }) + w.OnClick(func(e events.Event) { + if !gui.IsRunning { + gui.IsRunning = true + tb.Restyle() + go func() { + loops.Run(curMode) + gui.Stopped() + }() + } }) + }) - stepN := make(map[string]int) - steps := loops.Stacks[mode].Order - stringToEnumTime := make(map[string]etime.Times) - for _, st := range steps { - stepN[st.String()] = 1 - stringToEnumTime[st.String()] = st - } + tree.AddAt(p, "loop-step", func(w *core.Button) { + tb := gui.Toolbar + w.SetText("Step").SetIcon(icons.SkipNext). + SetTooltip("Step the current mode, according to the following step level and N") + w.FirstStyler(func(s *styles.Style) { + s.SetEnabled(!gui.IsRunning) + s.SetAbilities(true, abilities.RepeatClickable) + }) + w.OnClick(func(e events.Event) { + if !gui.IsRunning { + gui.IsRunning = true + tb.Restyle() + go func() { + st := loops.Stacks[curMode] + nst := int(stepNSpin.Value) + loops.Step(curMode, nst, st.StepLevel) + gui.Stopped() + }() + } + }) + }) - tree.AddAt(p, pfx+mode.String()+"-step", func(w *core.Button) { - tb := gui.Toolbar - w.SetText(pfx + "Step").SetIcon(icons.SkipNext). - SetTooltip("Step the " + pfx + mode.String() + " process according to the following step level and N") - w.FirstStyler(func(s *styles.Style) { - s.SetEnabled(!gui.IsRunning) - s.SetAbilities(true, abilities.RepeatClickable) - }) - w.OnClick(func(e events.Event) { - if !gui.IsRunning { - gui.IsRunning = true - tb.Restyle() - go func() { - stack := loops.Stacks[mode] - loops.Step(mode, stepN[stack.StepLevel.String()], stack.StepLevel) - gui.Stopped() - }() + tree.AddAt(p, "step-level", func(w *core.Chooser) { + stepChoose = w + updateSteps() + w.SetCurrentValue(curStep.String()) + w.OnChange(func(e events.Event) { + st := loops.Stacks[curMode] + cs := stepChoose.CurrentItem.Value.(string) + for _, l := range st.Order { + if l.String() == cs { + st.StepLevel = l + stepNSpin.Value = float32(st.Loops[l].StepCount) + stepNSpin.Update() + break } - }) + } }) + }) - var chs *core.Chooser - var sp *core.Spinner - tree.AddAt(p, pfx+mode.String()+"-level", func(w *core.Chooser) { - chs = w - stepStrs := []string{} - for _, s := range steps { - stepStrs = append(stepStrs, s.String()) + tree.AddAt(p, "step-n", func(w *core.Spinner) { + stepNSpin = w + w.SetStep(1).SetMin(1).SetValue(1) + w.SetTooltip("number of iterations per step") + w.OnChange(func(e events.Event) { + st := loops.Stacks[curMode] + if st != nil { + st.StepCount = int(w.Value) + st.Loops[st.StepLevel].StepCount = st.StepCount } - w.SetStrings(stepStrs...) - stack := loops.Stacks[mode] - w.SetCurrentValue(stack.StepLevel.String()) - w.OnChange(func(e events.Event) { - stack := loops.Stacks[mode] - stack.StepLevel = stringToEnumTime[chs.CurrentItem.Value.(string)] - sp.Value = float32(stepN[stack.StepLevel.String()]) - sp.Update() - }) }) + }) - tree.AddAt(p, pfx+mode.String()+"-n", func(w *core.Spinner) { - sp = w - w.SetStep(1).SetMin(1).SetValue(1) - w.SetTooltip("number of iterations per step") - w.OnChange(func(e events.Event) { - stepN[chs.CurrentItem.Value.(string)] = int(w.Value) - }) - }) - } } diff --git a/looper/loop.go b/looper/loop.go index 7b3719b0..e5b862c9 100644 --- a/looper/loop.go +++ b/looper/loop.go @@ -44,23 +44,43 @@ type Loop struct { // IsDone functions are called after each loop iteration, // and if any return true, then the loop iteration is terminated. IsDone NamedFuncs + + // StepCount is the default step count for this loop level. + StepCount int } // NewLoop returns a new loop with given Counter Max and increment. func NewLoop(ctrMax, ctrIncr int) *Loop { lp := &Loop{} lp.Counter.SetMaxInc(ctrMax, ctrIncr) + lp.StepCount = 1 return lp } -// AddEvent adds a new event at given counter. +// AddEvent adds a new event at given counter. If an event already exists +// for that counter, the function is added to the list for that event. func (lp *Loop) AddEvent(name string, atCtr int, fun func()) *Event { - ev := NewEvent(name, atCtr, fun) - lp.Events = append(lp.Events, ev) + ev := lp.EventByCounter(atCtr) + if ev == nil { + ev = NewEvent(name, atCtr, fun) + lp.Events = append(lp.Events, ev) + } else { + ev.OnEvent.Add(name, fun) + } return ev } -// EventByName returns event by name, nil if not found +// EventByCounter returns event for given atCounter value, nil if not found. +func (lp *Loop) EventByCounter(atCtr int) *Event { + for _, ev := range lp.Events { + if ev.AtCounter == atCtr { + return ev + } + } + return nil +} + +// EventByName returns event by name, nil if not found. func (lp *Loop) EventByName(name string) *Event { for _, ev := range lp.Events { if ev.Name == name { diff --git a/looper/stack.go b/looper/stack.go index f50bc119..4dbfeb4c 100644 --- a/looper/stack.go +++ b/looper/stack.go @@ -28,7 +28,11 @@ type Stack struct { // the beginning and shorter timescales like Trial should be and the end. Order []enums.Enum - // If true, stop model at the end of the current StopLevel. + // OnInit are functions to run for Init function of this stack, + // which also resets the counters for this stack. + OnInit NamedFuncs + + // StopNext will stop running at the end of the current StopLevel if set. StopNext bool // StopFlag will stop running ASAP if set. @@ -53,13 +57,13 @@ type Stack struct { // NewStack returns a new Stack for given mode. func NewStack(mode enums.Enum) *Stack { - stack := &Stack{} - stack.Init(mode) - return stack + st := &Stack{} + st.newInit(mode) + return st } -// Init initializes new data structures for a newly created object -func (st *Stack) Init(mode enums.Enum) { +// newInit initializes new data structures for a newly created object. +func (st *Stack) newInit(mode enums.Enum) { st.Mode = mode st.StepLevel = etime.Trial st.StepCount = 1 @@ -83,6 +87,24 @@ func (st *Stack) AddTime(time enums.Enum, ctrMax int) *Stack { return st } +// AddOnStartToAll adds given function taking mode and time args to OnStart in all loops. +func (st *Stack) AddOnStartToAll(name string, fun func(mode, time enums.Enum)) { + for tt, lp := range st.Loops { + lp.OnStart.Add(name, func() { + fun(st.Mode, tt) + }) + } +} + +// AddOnEndToAll adds given function taking mode and time args to OnEnd in all loops. +func (st *Stack) AddOnEndToAll(name string, fun func(mode, time enums.Enum)) { + for tt, lp := range st.Loops { + lp.OnEnd.Add(name, func() { + fun(st.Mode, tt) + }) + } +} + // AddTimeIncr adds a new timescale to this Stack with a given ctrMax number of iterations, // and increment per step. // The order in which this method is invoked is important, @@ -117,11 +139,21 @@ func (st *Stack) TimeBelow(time enums.Enum) enums.Enum { return etime.NoTime } +//////// Control + // SetStep sets stepping to given level and number of iterations. -func (st *Stack) SetStep(numSteps int, stopscale enums.Enum) { - st.StopLevel = stopscale +// If numSteps == 0 then the default for the given stops +func (st *Stack) SetStep(numSteps int, stopLevel enums.Enum) { + st.StopLevel = stopLevel + lp := st.Loops[stopLevel] + if numSteps > 0 { + st.StopCount = numSteps + lp.StepCount = numSteps + } else { + numSteps = lp.StepCount + } st.StopCount = numSteps - st.StepLevel = stopscale + st.StepLevel = stopLevel st.StepCount = numSteps st.StopFlag = false st.StopNext = true diff --git a/looper/stacks.go b/looper/stacks.go index 5f26e1f0..1c83d966 100644 --- a/looper/stacks.go +++ b/looper/stacks.go @@ -38,15 +38,15 @@ type Stacks struct { // NewStacks returns a new initialized collection of Stacks. func NewStacks() *Stacks { - ss := &Stacks{} - ss.Init() - return ss + ls := &Stacks{} + ls.newInit() + return ls } -// Init initializes the state of the stacks, to be called on a newly created object. -func (ss *Stacks) Init() { - ss.Stacks = map[enums.Enum]*Stack{} - ss.lastStartedCounter = map[Scope]int{} +// newInit initializes the state of the stacks, to be called on a newly created object. +func (ls *Stacks) newInit() { + ls.Stacks = map[enums.Enum]*Stack{} + ls.lastStartedCounter = map[Scope]int{} } //////// Run API @@ -54,50 +54,51 @@ func (ss *Stacks) Init() { // Run runs the stack of loops for given mode (Train, Test, etc). // This resets any stepping settings for this stack and runs // until completion or stopped externally. -func (ss *Stacks) Run(mode enums.Enum) { - ss.Mode = mode - ss.ClearStep(mode) - ss.Cont() +func (ls *Stacks) Run(mode enums.Enum) { + ls.Mode = mode + ls.ClearStep(mode) + ls.Cont() } // ResetAndRun calls ResetCountersByMode on this mode // and then Run. This ensures that the Stack is run from // the start, regardless of what state it might have been in. -func (ss *Stacks) ResetAndRun(mode enums.Enum) { - ss.ResetCountersByMode(mode) - ss.Run(mode) +func (ls *Stacks) ResetAndRun(mode enums.Enum) { + ls.ResetCountersByMode(mode) + ls.Run(mode) } // Cont continues running based on current state of the stacks. // This is common pathway for Step and Run, which set state and // call Cont. Programatic calling of Step can continue with Cont. -func (ss *Stacks) Cont() { - ss.isRunning = true - ss.internalStop = false - ss.runLevel(0) // 0 Means the top level loop - ss.isRunning = false +func (ls *Stacks) Cont() { + ls.isRunning = true + ls.internalStop = false + ls.runLevel(0) // 0 Means the top level loop + ls.isRunning = false } -// Step numSteps stopscales. Use this if you want to do exactly one trial -// or two epochs or 50 cycles or whatever -func (ss *Stacks) Step(mode enums.Enum, numSteps int, stopscale enums.Enum) { - ss.Mode = mode - st := ss.Stacks[ss.Mode] - st.SetStep(numSteps, stopscale) - ss.Cont() +// Step numSteps at given stopLevel. Use this if you want to do exactly one trial +// or two epochs or 50 cycles or whatever. If numSteps <= 0 then the default +// number of steps for given step level is used. +func (ls *Stacks) Step(mode enums.Enum, numSteps int, stopLevel enums.Enum) { + ls.Mode = mode + st := ls.Stacks[ls.Mode] + st.SetStep(numSteps, stopLevel) + ls.Cont() } // ClearStep clears stepping variables from given mode, // so it will run to completion in a subsequent Cont(). // Called by Run -func (ss *Stacks) ClearStep(mode enums.Enum) { - st := ss.Stacks[ss.Mode] +func (ls *Stacks) ClearStep(mode enums.Enum) { + st := ls.Stacks[ls.Mode] st.ClearStep() } // Stop stops currently running stack of loops at given run time level -func (ss *Stacks) Stop(level enums.Enum) { - st := ss.Stacks[ss.Mode] +func (ls *Stacks) Stop(level enums.Enum) { + st := ls.Stacks[ls.Mode] st.StopLevel = level st.StopCount = 0 st.StopFlag = true @@ -106,15 +107,15 @@ func (ss *Stacks) Stop(level enums.Enum) { //////// Config API // AddStack adds a new Stack for given mode -func (ss *Stacks) AddStack(mode enums.Enum) *Stack { - stack := NewStack(mode) - ss.Stacks[mode] = stack - return stack +func (ls *Stacks) AddStack(mode enums.Enum) *Stack { + st := NewStack(mode) + ls.Stacks[mode] = st + return st } // Loop returns the Loop associated with given mode and timescale. -func (ss *Stacks) Loop(mode, time enums.Enum) *Loop { - st := ss.Stacks[mode] +func (ls *Stacks) Loop(mode, time enums.Enum) *Loop { + st := ls.Stacks[mode] if st == nil { return nil } @@ -122,47 +123,77 @@ func (ss *Stacks) Loop(mode, time enums.Enum) *Loop { } // ModeStack returns the Stack for the current Mode -func (ss *Stacks) ModeStack() *Stack { - return ss.Stacks[ss.Mode] +func (ls *Stacks) ModeStack() *Stack { + return ls.Stacks[ls.Mode] } // AddEventAllModes adds a new event for all modes at given timescale. -func (ss *Stacks) AddEventAllModes(time enums.Enum, name string, atCtr int, fun func()) { - for _, stack := range ss.Stacks { - stack.Loops[time].AddEvent(name, atCtr, fun) +func (ls *Stacks) AddEventAllModes(time enums.Enum, name string, atCtr int, fun func()) { + for _, st := range ls.Stacks { + st.Loops[time].AddEvent(name, atCtr, fun) + } +} + +// AddOnStartToAll adds given function taking mode and time args to OnStart in all stacks, loops +func (ls *Stacks) AddOnStartToAll(name string, fun func(mode, time enums.Enum)) { + for _, st := range ls.Stacks { + st.AddOnStartToAll(name, fun) + } +} + +// AddOnEndToAll adds given function taking mode and time args to OnEnd in all stacks, loops +func (ls *Stacks) AddOnEndToAll(name string, fun func(mode, time enums.Enum)) { + for _, st := range ls.Stacks { + st.AddOnEndToAll(name, fun) } } //////// More detailed control API // IsRunning is True if running. -func (ss *Stacks) IsRunning() bool { - return ss.isRunning +func (ls *Stacks) IsRunning() bool { + return ls.isRunning +} + +// InitMode initializes [Stack] of given mode, +// resetting counters and calling the OnInit functions. +func (ls *Stacks) InitMode(mode enums.Enum) { + ls.ResetCountersByMode(mode) + st := ls.Stacks[mode] + st.OnInit.Run() } // ResetCountersByMode resets counters for given mode. -func (ss *Stacks) ResetCountersByMode(mode enums.Enum) { - for sk, _ := range ss.lastStartedCounter { +func (ls *Stacks) ResetCountersByMode(mode enums.Enum) { + for sk, _ := range ls.lastStartedCounter { skm, _ := sk.ModeTime() if skm == mode.Int64() { - delete(ss.lastStartedCounter, sk) + delete(ls.lastStartedCounter, sk) } } - for m, stack := range ss.Stacks { + for m, st := range ls.Stacks { if m == mode { - for _, loop := range stack.Loops { + for _, loop := range st.Loops { loop.Counter.Cur = 0 } } } } +// Init initializes all stacks. See [Stacks.InitMode] for more info. +func (ls *Stacks) Init() { + ls.lastStartedCounter = map[Scope]int{} + for _, st := range ls.Stacks { + ls.InitMode(st.Mode) + } +} + // ResetCounters resets the Cur on all loop Counters, // and resets the Stacks's place in the loops. -func (ss *Stacks) ResetCounters() { - ss.lastStartedCounter = map[Scope]int{} - for _, stack := range ss.Stacks { - for _, loop := range stack.Loops { +func (ls *Stacks) ResetCounters() { + ls.lastStartedCounter = map[Scope]int{} + for _, st := range ls.Stacks { + for _, loop := range st.Loops { loop.Counter.Cur = 0 } } @@ -170,26 +201,26 @@ func (ss *Stacks) ResetCounters() { // ResetCountersBelow resets the Cur on all loop Counters below given level // (inclusive), and resets the Stacks's place in the loops. -func (ss *Stacks) ResetCountersBelow(mode enums.Enum, time enums.Enum) { - for _, stack := range ss.Stacks { - if stack.Mode != mode { +func (ls *Stacks) ResetCountersBelow(mode enums.Enum, time enums.Enum) { + for _, st := range ls.Stacks { + if st.Mode != mode { continue } - for lt, loop := range stack.Loops { + for lt, loop := range st.Loops { if lt.Int64() > time.Int64() { continue } loop.Counter.Cur = 0 sk := ToScope(mode, lt) - delete(ss.lastStartedCounter, sk) + delete(ls.lastStartedCounter, sk) } } } // DocString returns an indented summary of the loops and functions in the stack. -func (ss *Stacks) DocString() string { +func (ls *Stacks) DocString() string { var sb strings.Builder - for _, st := range ss.Stacks { + for _, st := range ls.Stacks { sb.WriteString(st.DocString()) } return sb.String()