Skip to content

Extract evalService from debuggerController that serves console. #4992

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

Merged
merged 42 commits into from
Dec 28, 2022

Conversation

polina-c
Copy link
Contributor

@polina-c polina-c commented Dec 25, 2022

RELEASE_NOTE_EXCEPTION=[no functional changes]

@polina-c polina-c changed the title Console2 - eval service Extract eval service that serves console. Dec 27, 2022
@polina-c polina-c marked this pull request as ready for review December 27, 2022 17:35
@polina-c polina-c requested review from elliette and a team as code owners December 27, 2022 17:35
@polina-c polina-c requested review from bkonyi and removed request for a team December 27, 2022 17:35
@polina-c polina-c changed the title Extract eval service that serves console. Extract evalService from debuggerController that serves console. Dec 27, 2022
@@ -1154,7 +1154,7 @@ class _LineItemState extends State<LineItem>

if (word != '') {
try {
final response = await controller.evalAtCurrentFrame(word);
final response = await controller.evalService.evalAtCurrentFrame(word);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This case is tricky as you wouldn't want any injected variables from the console to show up when evaluating while hovering over source code. Maybe this case should take an extra named parameter like includeConsoleVariables: false

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussed and agreed evalAtCurrentFrame will never be invoked for global scope.

required this.service,
});

final IsolateRef? Function() isolateRef;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would seem more consistent to use ValueListenable for all of these rather than callbacks.

Copy link
Contributor Author

@polina-c polina-c Dec 27, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ValueListenable is too powerful. I want it to be clear that EvalService does not need to listen for the changes. It just needs up-to-date value.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't quite buy this. The autocomplete cache is bogus if the isolate changed, for example. ValueListenable does not force you to always listen for changes. As appropriate you can always just use .value to get the current value. On the other end of the spectrum, VmServiceWrapper needs to just be a fixed instance because there is no reason anyone writing this code should worry it can changed.,

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My goal is clarity of contract for the method.
I want the constructor parameters to be smallest needed set of what the object needs to function. Of course, not at cost of convenience.

About ValueListenable vs function. I remember a story: when I passed ValueListenable, I got comments in code review like 'why this object need to listen to every change? it is performance overhead'. And then I needed to explain that the object just needs values.

So, it affects readability of code too.

Does it change something for you?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

About VmServiceWrapper: as I understand when reconnect happens, DebuggerController is not recreated, but it resets some variables.
So, EvalService will not recreate, but just reset.

Yes, it may be not the best implementation, but it is what we currently have. And it will take more refactoring to change.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And, I did not understand you point about consistency. In Flutter framework there is ton of methods and constructors that take functions, not listeners.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Each time an app connects you get a new DebuggerController. How it happens flows back to how the provider for the DebuggerController is initialized.
The private getter for the service in debuggerController is really just calling
serviceManager.service!
Would be clearer to just call that global from EvalService rather than making it look like it is injected in.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, I think ValueListenable is almost always the right choice if something can actually change. If it can't change then pass in the actual value when initializing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Converted service to variable, isPaused and variables to listenable.


String get _isolateRefId {
final id = isolateRef()?.id;
if (id == null) return '';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is returning '' for the isolateRefId ever ok?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not know. I copied it from previous code. Added 'TODO'.

/// The return value can be one of [Obj] or [Sentinel].
Future<Obj> getObject(ObjRef objRef) {
return _service.getObject(_isolateRefId, objRef.id!);
evalService.cache.clear();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is badly coupled. Instead EvalService should listen for isolate changes itself instead of requiring the debuggerControl to call this method at the appropriate time.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks. fixed.

late final EvalService evalService = EvalService(
isolateRef: isolateRef,
variables: variables,
frameForEval: () => _frameForEval,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_frameForEval should also be made a ValueListenable for consistency. That would probably cleanup some of the debugger UI that should be listening for changes to the frameForEval.

Copy link
Contributor Author

@polina-c polina-c Dec 27, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does not seem to make sense for me to recalculate and update frameForEval every time _selectedStackFrame or _stackFramesWithLocation change, from both code readability/simplicity and performance perspective.

Deleted getter and moved the logic to the function, as the getter is not used in other places..

}
}

final codeViewController = CodeViewController();

late final EvalService evalService = EvalService(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make this final instead of late final.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initializer cannot use getters:

Screenshot 2022-12-27 at 2 56 24 PM

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. Move the initializer to the constructor to avoid this issue. In general, I would avoid initializing fields inline except for very trivial cases. Otherwise the initialization order semantics can trip some people up when reviewing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even after the move it is still late, because it cannot be initialized in constructor initializers, but only in the body. Did i miss something? Let's follow up then.

Copy link
Contributor

@jacob314 jacob314 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll be out tomorrow. Would be nice to remove the late final moving it to a constructor.

@polina-c polina-c merged commit fad158b into flutter:master Dec 28, 2022
@polina-c polina-c deleted the console2-evalService branch December 28, 2022 20:34
@CoderDake CoderDake mentioned this pull request Jan 24, 2023
7 tasks
@DartDevtoolWorkflowBot DartDevtoolWorkflowBot mentioned this pull request Jan 24, 2023
7 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants