Closed SaadArdati closed 2 years ago
It's almost definitely because its from a separate project. 🤞 But better errors would go a long way
I split off our API and now all the files ONLY depend on dart and the dart js commands works FLAWLESSLY.
I am still, however, getting the EXACT same error. The error message suggests creating new workers is failing for some reason. I'm assuming it's unable to locate the file?
Ah, the issue was this:
because the generated file goes into the root of the /web folder, not /web/workers.
Despite this, now I'm getting a new error
Expected a value of type 'FutureOr<Map<String, dynamic>>?', but got one of type 'LinkedMap<dynamic, dynamic>'
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart 251:49 throw_
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart 84:3 castError
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart 452:10 cast
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/types.dart 367:9 as
packages/squadron/src/browser/channel.dart 63:18 <fn>
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart 334:14 _checkAndCall
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart 339:39 dcall
dart-sdk/lib/html/dart2js/html_dart2js.dart 37230:58 <fn>
dart-sdk/lib/async/zone.dart 1444:13 _rootRunUnary
dart-sdk/lib/async/zone.dart 1335:19 runUnary
dart-sdk/lib/async/zone.dart 1244:7 runUnaryGuarded
dart-sdk/lib/async/zone.dart 1281:26 <fn>
Hi Saad, I'll have a look, will keep you posted. Could you post the definition of the service interface + service implementation? It looks like something (the ServiceImpl?) is returning a Map where the expected result would be a Future
That would be very unusual since the vm (macos window) app runs perfectly, why would the worker behave with a different exception like this?
In any case, here is the ServiceImpl:
import 'dart:async';
import 'package:squadron/squadron.dart';
import '../position_manager_thread.dart';
abstract class SolverService {
FutureOr<Map<String, dynamic>> initRoot(Map<String, dynamic> rootNodeJson);
FutureOr<Map<String, dynamic>> performLayout(
Set<Map<String, dynamic>> nodesReference,
Set<String> createdNodes,
Set<String> removedNodes,
Set<String> adoptedNodes,
Set<String> droppedNodes,
Set<String> changedNodes,
);
static const cmdPerformLayout = 1;
static const cmdInitRoot = 2;
}
class SolverServiceImpl implements SolverService, WorkerService {
final PositionManagerThread positionManager =
PositionManagerThread(notifier: (pos) => print(pos));
@override
FutureOr<Map<String, dynamic>> initRoot(Map<String, dynamic> rootNodeJson) =>
positionManager.initRoot(rootNodeJson).toJson();
@override
FutureOr<Map<String, dynamic>> performLayout(
Set<Map<String, dynamic>> nodesReference,
Set<String> createdNodes,
Set<String> removedNodes,
Set<String> adoptedNodes,
Set<String> droppedNodes,
Set<String> changedNodes,
) async {
return positionManager
.performLayout(
nodesReference,
createdNodes,
removedNodes,
adoptedNodes,
droppedNodes,
changedNodes,
)
.toJson();
}
@override
late final Map<int, CommandHandler> operations = {
SolverService.cmdPerformLayout: (WorkerRequest r) => performLayout(
rebuildSet<Map<String, dynamic>>(r.args[0]),
rebuildSet<String>(r.args[1]),
rebuildSet<String>(r.args[2]),
rebuildSet<String>(r.args[3]),
rebuildSet<String>(r.args[4]),
rebuildSet<String>(r.args[5]),
),
SolverService.cmdInitRoot: (WorkerRequest r) => initRoot(
rebuildMap(r.args[0]),
),
};
static Set<T> rebuildSet<T>(List items) => items.cast<T>().toSet();
static Map<String, dynamic> rebuildMap(Map dict) =>
dict.map((key, value) => MapEntry<String, dynamic>(key, value));
static Set<Map<String, dynamic>> rebuildMapSet(List<Map> items) => items
.map((item) => rebuildMap(item))
.cast<Map<String, dynamic>>()
.toSet();
}
pool
// this is a helper file to expose Squadron workers and worker pools as a SolverService
import 'dart:async';
import 'package:squadron/squadron.dart';
import 'solver_service.dart';
// Implementation of SolverService as a Squadron worker
class SolverWorker extends Worker implements SolverService {
SolverWorker(dynamic entryPoint, {String? id, List args = const []})
: super(entryPoint, id: id, args: args);
@override
FutureOr<Map<String, dynamic>> initRoot(Map<String, dynamic> rootNodeJson) {
return send(
SolverService.cmdInitRoot,
[
rootNodeJson,
],
);
}
@override
FutureOr<Map<String, dynamic>> performLayout(
Set<Map<String, dynamic>> nodes,
Set<String> createdNodes,
Set<String> removedNodes,
Set<String> adoptedNodes,
Set<String> droppedNodes,
Set<String> changedNodes,
) {
return send(
SolverService.cmdPerformLayout,
[
nodes.toList(),
createdNodes.toList(),
removedNodes.toList(),
adoptedNodes.toList(),
droppedNodes.toList(),
changedNodes.toList(),
],
);
}
}
and the browser activator + worker
import '../solver_worker_pool.dart' show SolverWorker;
SolverWorker createWorker() => SolverWorker('/solver_worker.dart.js');
import 'package:squadron/squadron_service.dart';
import '../solver_service.dart';
void main() => run((startRequest) => SolverServiceImpl());
I see your point but maybe there's a difference in Squadron's behavior between native and browser. Typically the Channel implementation is different. The native version can maybe cope with the situation but not the browser? I'll check my code.
In the meantime I see a difference between initRoot
and performLayout
in SolverServiceImpl
: both return a FutureOr<Map>
, both results are produced by a call to toJson()
, but one is marked async
and not the other. FutureOr<T>
is a strange beast in Dart, analogous to a TypeScript union type, meaning that it can be a T
or a Future<T>
. I try to handle this situation in the code but maybe something slipped through... From my experience I believe FutureOr
is good to use for abstract methods where you may accept sync or async implementations. But I try to avoid it for concrete implementations and prefer to use the appropriate T
or Future<T>
.
What is toJson()
? I suspect it's a sync method? Can you try this in SolverServiceImpl
:
@override
Map<String, dynamic> initRoot(Map<String, dynamic> rootNodeJson) =>
positionManager.initRoot(rootNodeJson).toJson();
@override
Map<String, dynamic> performLayout( ... ) /* no async */ =>
positionManager.performLayout( ... ).toJson();
On my side I will investigate why it works on native but not on browser.
You are totally correct that I'm missing an async! Incredible eye you have!
I also hate FutureOr as it is ambiguous while I prefer strongly and explicitly typed code.
In any case, I removed the async from performLayout because you are correct, it is a synchronous function adn the whole point is to make it asynchronous via isolates/workers.
Unfortunately, it still throws an error
Expected a value of type 'FutureOr<Map<String, dynamic>>?', but got one of type 'LinkedMap<dynamic, dynamic>'
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart 251:49 throw_
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart 84:3 castError
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart 452:10 cast
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/types.dart 367:9 as
packages/squadron/src/browser/channel.dart 63:18 <fn>
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart 334:14 _checkAndCall
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart 339:39 dcall
dart-sdk/lib/html/dart2js/html_dart2js.dart 37277:58 <fn>
dart-sdk/lib/async/zone.dart 1442:13 _rootRunUnary
dart-sdk/lib/async/zone.dart 1335:19 runUnary
dart-sdk/lib/async/zone.dart 1244:7 runUnaryGuarded
dart-sdk/lib/async/zone.dart 1281:26 <fn>
I'm going to do something you suggested previously in my last issue and that's to remove the generic types.
Yes, Isolates can be forgiving as it will accept strong types when code runs in the same process. I believe JavaScript is not :-(
Just thinking, did you recompile the Web worker after removing the async? Any change that impacts code running in a Web worker requires rebuilding it.
I did, yes. Still need some time to remove generics though and I heavily suspect it's the issue because the error message seems to be a casting error
Seems to be working now!! I'll report if I find any new issues :)
Great news!
FYI the latest version of Squadron includes logging capabilities to help you debug worker code. It can be difficult with JavaScript workers because they are not connected to the Dart debugger.
Typically you would do this:
void main() => run((startRequest) {
Squadron.logLevel = SquadronLogLevel.ALL; // optional, the log level set in your main app will be applied in workers by default
Squadron.logger = ConsoleSquadronLogger(); // logs to browser console
return SolverServiceImpl();
});
And in your service code you can then use Squadron.info()
, Squadron.warning()
, ect. to see what's going on.
In you main app, set the log level as appropriate, it will be passed on to the workers when they start up. In you main app and in Isolate workers, you can use DevSquadronLogger
instead of ConsoleSquadronLogger
.
Excellent to know!! Add this to the main pub page :) I'll be using this for sure.
You should add everything we've learned in the best issues into the pub page actually!
Will sure do! And don't hesitate to like the package on pub.dev ;-) or github... or both!!
I got the dart js command to run this time. Turns out, the SDK (that we made) that the solver classes depend on was depending on dart:ui classes for unrelated sections of the api.
I guess the dart js command isn't smart enough to tree-shake all the code that isn't being used by our api accessors.
Point is, I made a copy of the sdk and stripped it down heavely to only bare-minimum data without relying on any dart:ui or flutter code, and it worked. I compiled the js file and moved it into the web folder, but it's crashing and the error message is not useful unfortunately.
Maybe it's because it's a js file compiled from a completely separate and incompatible copy of the sdk, but in any case, an error message would go a long way.
Here's the ouput after running the project in a chrome browser: