[Lazarus] Events to be more frontend centric [[was: Re: Playing with debuggers]]

Martin Frb lazarus at mfriebe.de
Sun Nov 21 17:23:53 CET 2021

> 1) Events to be more frontend centric.
> 2) Actions (eval watch) to be triggered by the frontend
> 3) Representation of an "watch"  (Should methods be on the Watch 
> object, or the Debugger object)

=====> 1) Events to be more frontend centric.

The current events and especially states are certainly in need of clean up.

I would not say that they are all backend centric.
States like: dsRun, dsPause, dsStop  are very valid and meaningful for 
the frontends too.

On the other hand states like
- dsDestroying is backend only, and should not be exposed.
- dsInit, dsInternalPause are not well defined at all.

Looking at your code, I do not agree with your new states.
>   TLazExtEvaluationState = (
>     esNone,                  // No debugger available
>     esEvaluationImpossible,  // Evaluation of variables is impossible 
> (Maybe because there is nu debugee? (stopped))
>     esEvaluationPaused,      // Evaluation of variables is temporarily 
> disabled (Maybe because debugee is running?)
>     esEvaluationPossible     // Evaluation of variables is possible 
> (Debugee is probably paused)
>   );

1 - First of all,
The above are either
a- not enough
b- to many

a) There may be different extends of evaluation in the frontend. So more 
info than the above few states is needed.
=> Of course, the states may be sufficient, as further info could be 
maintained within the frontend.
* I quite often use breakpoints, that do not "break" (continue 
automatically), but take a history snapshot.
Currently when this happens the watch/stack/.. windows actually do 
update (and that is a workaround to missing options).
But ideally they should not. Only the history-snapshot should run.
* Or while viewing history => one can get existing values, but not 
evaluate new ones.

b) On the other hand, why differ between Paused and Impossible? They 
both mean "can't eval".
Your frontend may differ between the 2 reasons behind them. But other 
Frontends, relay on other factors.

After writing all the below, I realized that (probably) your frontend 
uses the 2 states to decide, if there should be a timeout before 
clearing the values?
But that is very specific to your frontend (well most frontends probably 
would benefit). But then this is just a duplication of the existing and 
further needed dsStop.

See idea on "CurrentCapacities" below.

2 - Second,
they already exist:
    WatchValue.Validity => ddsUnknown, ddsRequested, ddsEvaluating,  
ddsValid, ddsInvalid, ddsError
Though they are only set, once a value gets requested.
(Asking upfront, may be an idea, but one only need "function CanEval: 
boolean". / See idea on "CurrentCapacities" below.)

This falls partly under the topic (3) representation => should the state 
come via the watch or the debugger.

And then, if history is shown, there is no point of calling a function 
on the debugger. There is no data to be computed by the debugger.

3 - Third
Also, while your argument for a frontend-needs driven event system is 
compiling, we need to keep in mind, that the frontend (or plural 
frontends) may change/differ how they want to react to different backend 
The backends can not actually know this. So the frontend still needs 
(most of) the DebuggerStates (dsPause, dsRun, dsStopped)

Yet, as mentioned above, (sub)states may need to be added for different 
"pause reasons".
This may be something (some/all) frontends can do on their own. But some 
may also belong into the backend. (Maybe such as, that the backend is 
going to continue the run/step)

If a breakpoint has "no break" or "auto-continue" (the latter differs 
from the first), then parts of the frontend may need to know.
This can to some extend be managed in the frontend itself, by examining 
the current breakpoint(s) [1].
But it can't be entirely taken from the backend, because if the backends 
state is "mid single step", then the frontend can not sent a "step over".
If maintaining some of the state info is moved to the frontend, this 
must avoid duplicating info that is bound to the backend. (to avoid them 
becoming out of sync).

[1] Btw, off topic, there may be more than one breakpoint on the same 
address, so there needs to be a list of breakpoints hit...

===> Going forward

It might be best to look at an actual list of new states and events.

- What do different parts of the frontend need to react to (events)?
   And can/should the backend(s) know about each of those "causes".

- What states are important, from a frontend/users view.
- What mapping, between abilities and state do we need, and where to 
compute them.

For example the debugger already has
     function  GetCommands: TDBGCommands; virtual; // property Commands
And this returns what is currently possible
     if dcEvaluate in Commands then {watches can be evaluated}
- Except, that may be returning wrong for dsInternalPause => so there is 
some fixing needed.
- The naming is bad, because its more than just commands. Maybe should be:
    ~ dcEvaluate in CurrentCapacities
    ~ Debugger.Is[Currently]Capable(dcEvaluate)
    ~ dcEvaluate may be to narrow, if it includes ability to 
read/provide stack/thread/... Or maybe there can be separate enums.

As for events, here are the events that currently can trigger watches to 
be refreshed:

By the debugger (IIRC)
   WatchesNotification.OnUpdate    := @WatchUpdate;
     // send by the debugger  WITH watch=NIL, equal to the callback in 
your code
     // send by the debugger  WITH watch<>NIL, equal to 
   ThreadsNotification.OnCurrent   := @ContextChanged;
   CallstackNotification.OnCurrent := @ContextChanged;

By the frontend itself
   WatchesNotification.OnAdd       := @WatchAdd;
   WatchesNotification.OnRemove    := @WatchRemove;
   SnapshotNotification.OnCurrent  := @SnapshotChanged;

WatchesNotification.OnUpdate could be split into 2 events.
It is also the same for start/end availability of data.
That may want to be extended. Though the info can already be retrieved 
from the debugger. (DebuggerState / Commands)

Yet it is not even needed to get the state. (Or should not, I have to 
check if it works).
The watchvalues validity should be enough.
If correct, it should be ddsInvalid.
If a "ddsEvalNotPossible" is added, then the info is avail (and the 
timeout can be set, depending on debuggerState)

The threads and callstack need to be separate notification, Different 
windows may subscribe to different sets of them.

So I am not sure, if indeed we need an entire new event system.
One could argue that thread and stack should trigger 
But that would be wrong.
=> I have often thought it would be good to have a 2nd (or more) watch 
window, and have a setting for that window to stay at a fixed 
thread/stack, even if the selection in the stack window is changed.
- So such a window, would not change if the current stack in the 
stackwindow was changed
- But it would need to reload, if the user changed the value of a watch, 
and therefore modified the target memory. (which could affect other 
watches too)

So in the end, the watches window needs to be able to decide which 
events it wants.

On 14/09/2021 11:00, Joost van der Sluis via lazarus wrote:
> I have something similar as the data-monitors.
> Main difference is that the events are based on what the gui needs, 
> instead of what the debugger does.
> So: there is an event to tell the 'gui' that it is inpossible to show 
> any data. (for example: the application has stopped or there is no 
> debugger at all) There is an event for the case that debug-data has 
> become available or unavailable. (Application paused or continued)

  We have an event, when watches can start to be evaled 
See above.

> The advantage of 'events' that are more geared towards the gui, is 
> that it is easier to add catchy things. For example: to avoid 
> flickering, when the gui receives an event that debug-data is not 
> available anymore, it starts a timer of 200 mseconds, and only after 
> that timer the data is removed from the screen. This way, when 
> stepping through the code, there is no flickering in the small periods 
> of time that the application runs.
> It also made it easy to highlight variables that changed.
Both can be done, with the existing system. Though some optimizations 
can be applied.

More information about the lazarus mailing list