Skip to content

Commit

Permalink
Merge pull request #42 from Bennik2000/fix/change-week-jitter
Browse files Browse the repository at this point in the history
Move rapla result parsing into separate isolate
  • Loading branch information
Bennik2000 authored Sep 8, 2020
2 parents 1ed671c + 32ca683 commit ce5d110
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 64 deletions.
2 changes: 1 addition & 1 deletion lib/common/appstart/app_initializer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Future<void> initializeApp(bool isBackground) async {

WidgetsFlutterBinding.ensureInitialized();

injectServices();
injectServices(isBackground);

if (isBackground) {
await LocalizationInitialize.fromPreferences(KiwiContainer().resolve())
Expand Down
9 changes: 7 additions & 2 deletions lib/common/appstart/service_injector.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import 'package:dhbwstudentapp/schedule/business/schedule_source_setup.dart';
import 'package:dhbwstudentapp/schedule/data/schedule_entry_repository.dart';
import 'package:dhbwstudentapp/schedule/data/schedule_query_information_repository.dart';
import 'package:dhbwstudentapp/schedule/service/error_report_schedule_source_decorator.dart';
import 'package:dhbwstudentapp/schedule/service/isolate_schedule_source_decorator.dart';
import 'package:dhbwstudentapp/schedule/service/rapla/rapla_schedule_source.dart';
import 'package:dhbwstudentapp/schedule/service/schedule_source.dart';
import 'package:kiwi/kiwi.dart';
Expand All @@ -22,7 +23,7 @@ bool _isInjected = false;
/// This function injects all instances into the KiwiContainer. You can get a
/// singleton instance of a registered type using KiwiContainer().resolve()
///
void injectServices() {
void injectServices(bool isBackground) {
if (_isInjected) return;

KiwiContainer c = KiwiContainer();
Expand All @@ -31,7 +32,11 @@ void injectServices() {
SecureStorageAccess(),
));
c.registerInstance<ScheduleSource>(
ErrorReportScheduleSourceDecorator(RaplaScheduleSource()),
isBackground
? ErrorReportScheduleSourceDecorator(RaplaScheduleSource())
: IsolateScheduleSourceDecorator(
ErrorReportScheduleSourceDecorator(RaplaScheduleSource()),
),
);
c.registerInstance(DatabaseAccess());
c.registerInstance(ScheduleEntryRepository(
Expand Down
106 changes: 106 additions & 0 deletions lib/schedule/service/isolate_schedule_source_decorator.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import 'dart:async';
import 'dart:isolate';

import 'package:dhbwstudentapp/common/util/cancellation_token.dart';
import 'package:dhbwstudentapp/schedule/model/schedule.dart';
import 'package:dhbwstudentapp/schedule/service/schedule_source.dart';

///
/// ScheduleSource decorator which executes the [querySchedule] in a separated isolate
///
class IsolateScheduleSourceDecorator extends ScheduleSource {
final ScheduleSource _scheduleSource;

Stream _isolateToMain;
Isolate _isolate;
SendPort _sendPort;

IsolateScheduleSourceDecorator(this._scheduleSource);

@override
Future<Schedule> querySchedule(DateTime from, DateTime to,
[CancellationToken cancellationToken]) async {
await _initializeIsolate();

// Use the cancellation token to send a cancel message.
// The isolate then uses a new instance to cancel the request
cancellationToken.setCancellationCallback(() {
_sendPort.send({"type": "cancel"});
});

_sendPort.send({
"type": "execute",
"source": _scheduleSource,
"from": from,
"to": to,
});

final completer = Completer<Schedule>();
final subscription = _isolateToMain.listen((result) {
cancellationToken.setCancellationCallback(null);
completer.complete(result);
});

final result = await completer.future;
subscription.cancel();

return result;
}

Future<void> _initializeIsolate() async {
if (_isolate != null && _isolateToMain != null && _sendPort != null) return;

var isolateToMain = ReceivePort();

// Use a broadcast stream. The normal ReceivePort closes after one subscription
_isolateToMain = isolateToMain.asBroadcastStream();
_isolate = await Isolate.spawn(
scheduleSourceIsolateEntryPoint, isolateToMain.sendPort);
_sendPort = await _isolateToMain.first;
}

@override
void setEndpointUrl(String url) {
_scheduleSource.setEndpointUrl(url);
}

@override
void validateEndpointUrl(String url) {
_scheduleSource.validateEndpointUrl(url);
}
}

void scheduleSourceIsolateEntryPoint(SendPort sendPort) async {
// Using the given send port, send back a send port for two way communication
var port = ReceivePort();
sendPort.send(port.sendPort);

CancellationToken token;

await for (var message in port) {
if (message["type"] == "execute") {
token = CancellationToken();
executeQueryScheduleMessage(message, sendPort, token);
} else if (message["type"] == "cancel") {
token?.cancel();
}
}
}

Future<void> executeQueryScheduleMessage(
Map<String, dynamic> map,
SendPort sendPort,
CancellationToken token,
) async {
try {
ScheduleSource source = map["source"];
DateTime from = map["from"];
DateTime to = map["to"];

var result = await source.querySchedule(from, to, token);

sendPort.send(result);
} on OperationCancelledException catch (_) {
sendPort.send(null);
}
}
19 changes: 6 additions & 13 deletions lib/schedule/ui/viewmodels/weekly_schedule_view_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import 'dart:async';
import 'dart:math';

import 'package:dhbwstudentapp/common/ui/viewmodels/base_view_model.dart';
import 'package:dhbwstudentapp/common/util/cancelable_mutex.dart';
import 'package:dhbwstudentapp/common/util/cancellation_token.dart';
import 'package:dhbwstudentapp/common/util/date_utils.dart';
import 'package:dhbwstudentapp/schedule/business/schedule_provider.dart';
import 'package:dhbwstudentapp/schedule/model/schedule.dart';
import 'package:dhbwstudentapp/schedule/service/schedule_source.dart';
import 'package:mutex/mutex.dart';

class WeeklyScheduleViewModel extends BaseViewModel {
static const Duration weekDuration = Duration(days: 7);
Expand Down Expand Up @@ -35,8 +35,7 @@ class WeeklyScheduleViewModel extends BaseViewModel {
Timer _errorResetTimer;
Timer _updateNowTimer;

CancellationToken _updateScheduleCancellationToken;
Mutex _mutex = Mutex();
final CancelableMutex _updateMutex = CancelableMutex();
DateTime lastRequestedStart;
DateTime lastRequestedEnd;

Expand Down Expand Up @@ -102,35 +101,29 @@ class WeeklyScheduleViewModel extends BaseViewModel {
lastRequestedEnd = end;
lastRequestedStart = start;

_updateScheduleCancellationToken?.cancel();

await _mutex.acquire();

_updateScheduleCancellationToken = null;
await _updateMutex.acquireAndCancelOther();

if (lastRequestedStart != start || lastRequestedEnd != end) {
_mutex.release();
_updateMutex.release();
return;
}

_updateScheduleCancellationToken = new CancellationToken();

try {
isUpdating = true;
notifyListeners("isUpdating");

await _doUpdateSchedule(start, end);
} catch (_) {} finally {
isUpdating = false;
_mutex.release();
_updateMutex.release();
notifyListeners("isUpdating");
}
}

Future _doUpdateSchedule(DateTime start, DateTime end) async {
print("Refreshing schedule...");

var cancellationToken = _updateScheduleCancellationToken;
var cancellationToken = _updateMutex.token;

var cachedSchedule = await scheduleProvider.getCachedSchedule(start, end);
cancellationToken.throwIfCancelled();
Expand Down
100 changes: 52 additions & 48 deletions lib/schedule/ui/weeklyschedule/weekly_schedule_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -74,23 +74,7 @@ class _WeeklySchedulePageState extends State<WeeklySchedulePage> {
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
FlatButton(
child: Icon(Icons.chevron_left),
onPressed: _previousWeek,
),
FlatButton(
child: Icon(Icons.today),
onPressed: _goToToday,
),
FlatButton(
child: Icon(Icons.chevron_right),
onPressed: _nextWeek,
),
],
),
_buildNavigationButtonBar(),
Expanded(
child: Stack(
children: <Widget>[
Expand All @@ -99,38 +83,38 @@ class _WeeklySchedulePageState extends State<WeeklySchedulePage> {
child: PropertyChangeConsumer(
properties: ["weekSchedule", "now"],
builder: (BuildContext context,
WeeklyScheduleViewModel model,
Set properties) =>
PageTransitionSwitcher(
reverse: !model.didUpdateScheduleIntoFuture,
duration: Duration(milliseconds: 300),
transitionBuilder: (Widget child,
Animation<double> animation,
Animation<double> secondaryAnimation) =>
SharedAxisTransition(
child: child,
animation: animation,
secondaryAnimation: secondaryAnimation,
transitionType:
SharedAxisTransitionType.horizontal,
),
child: ScheduleWidget(
key: ValueKey(
model.currentDateStart.toIso8601String(),
WeeklyScheduleViewModel model, Set properties) {
return PageTransitionSwitcher(
reverse: !model.didUpdateScheduleIntoFuture,
duration: Duration(milliseconds: 300),
transitionBuilder: (Widget child,
Animation<double> animation,
Animation<double> secondaryAnimation) =>
SharedAxisTransition(
child: child,
animation: animation,
secondaryAnimation: secondaryAnimation,
transitionType:
SharedAxisTransitionType.horizontal,
),
child: ScheduleWidget(
key: ValueKey(
model.currentDateStart.toIso8601String(),
),
schedule: model.weekSchedule,
displayStart: model.clippedDateStart ??
model.currentDateStart,
displayEnd: model.clippedDateEnd ??
model.currentDateEnd,
onScheduleEntryTap: (entry) {
_onScheduleEntryTap(context, entry);
},
now: model.now,
displayEndHour: model.displayEndHour,
displayStartHour: model.displayStartHour,
),
schedule: model.weekSchedule,
displayStart: model.clippedDateStart ??
model.currentDateStart,
displayEnd:
model.clippedDateEnd ?? model.currentDateEnd,
onScheduleEntryTap: (entry) {
_onScheduleEntryTap(context, entry);
},
now: model.now,
displayEndHour: model.displayEndHour,
displayStartHour: model.displayStartHour,
),
),
);
},
),
),
PropertyChangeConsumer(
Expand All @@ -154,6 +138,26 @@ class _WeeklySchedulePageState extends State<WeeklySchedulePage> {
);
}

Row _buildNavigationButtonBar() {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
FlatButton(
child: Icon(Icons.chevron_left),
onPressed: _previousWeek,
),
FlatButton(
child: Icon(Icons.today),
onPressed: _goToToday,
),
FlatButton(
child: Icon(Icons.chevron_right),
onPressed: _nextWeek,
),
],
);
}

Widget buildErrorDisplay(BuildContext context) {
return PropertyChangeConsumer(
properties: [
Expand Down

0 comments on commit ce5d110

Please sign in to comment.