croupier: Combine Watch for Game Setup and Log
By combining these two, we end up with a much faster Resume Game
experience and fewer ongoing watch streams.
Reasons:
- slow to have 2 watch streams
- resume game would still animate the final scanned moves because
the game log's watch wasn't the one we wait for when restarting a
game
Tested using Solitaire on 2 devices and restarting them.
Change-Id: I89a26614449dfd0e46cf87645ca8532cf6176aa2
diff --git a/Makefile b/Makefile
index dca4438..24f222a 100644
--- a/Makefile
+++ b/Makefile
@@ -189,13 +189,16 @@
mock:
mv lib/src/syncbase/log_writer.dart lib/src/syncbase/log_writer.dart.backup
mv lib/src/syncbase/settings_manager.dart lib/src/syncbase/settings_manager.dart.backup
+ mv lib/src/syncbase/util.dart lib/src/syncbase/util.dart.backup
cp lib/src/mocks/log_writer.dart lib/src/syncbase/
cp lib/src/mocks/settings_manager.dart lib/src/syncbase/
+ cp lib/src/mocks/util.dart lib/src/syncbase/
.PHONY: unmock
unmock:
mv lib/src/syncbase/log_writer.dart.backup lib/src/syncbase/log_writer.dart
mv lib/src/syncbase/settings_manager.dart.backup lib/src/syncbase/settings_manager.dart
+ mv lib/src/syncbase/util.dart.backup lib/src/syncbase/util.dart
.PHONY: env-check
env-check:
diff --git a/lib/logic/croupier.dart b/lib/logic/croupier.dart
index 146d5d3..5c33aa2 100644
--- a/lib/logic/croupier.dart
+++ b/lib/logic/croupier.dart
@@ -51,7 +51,8 @@
_updateSettingsEveryoneCb,
_updateGamesFoundCb,
_updatePlayerFoundCb,
- _updateGameStatusCb);
+ _updateGameStatusCb,
+ _gameLogUpdateCb);
settings_manager.load().then((String csString) {
settings = new CroupierSettings.fromJSONString(csString);
@@ -84,6 +85,12 @@
}
}
+ Future _gameLogUpdateCb(String key, String value, bool duringScan) async {
+ if (game != null && game.gamelog.watchUpdateCb != null) {
+ await game.gamelog.watchUpdateCb(key, value, duringScan);
+ }
+ }
+
int userIDFromPlayerNumber(int playerNumber) {
return players_found.keys.firstWhere(
(int user) => players_found[user] == playerNumber,
@@ -107,7 +114,7 @@
void _quitGame() {
if (game != null) {
- game.quit();
+ settings_manager.quitGame();
game = null;
}
}
diff --git a/lib/logic/game/game.dart b/lib/logic/game/game.dart
index 1e139a3..1b95cb1 100644
--- a/lib/logic/game/game.dart
+++ b/lib/logic/game/game.dart
@@ -9,6 +9,7 @@
import '../card.dart' show Card;
import '../../src/syncbase/log_writer.dart' show SimulLevel;
+import '../../src/syncbase/util.dart';
part 'game_def.part.dart';
part 'game_command.part.dart';
diff --git a/lib/logic/game/game_def.part.dart b/lib/logic/game/game_def.part.dart
index 43deaa6..b6d5cd2 100644
--- a/lib/logic/game/game_def.part.dart
+++ b/lib/logic/game/game_def.part.dart
@@ -149,10 +149,6 @@
deck.addAll(Card.All);
}
- void quit() {
- this.gamelog.close();
- }
-
// UNIMPLEMENTED
void move(Card card, List<Card> dest);
void triggerEvents();
diff --git a/lib/logic/game/game_log.part.dart b/lib/logic/game/game_log.part.dart
index ea238cd..f502184 100644
--- a/lib/logic/game/game_log.part.dart
+++ b/lib/logic/game/game_log.part.dart
@@ -9,7 +9,8 @@
List<GameCommand> log = new List<GameCommand>();
// This list is normally empty, but may grow if multiple commands arrive.
List<GameCommand> pendingCommands = new List<GameCommand>();
- bool hasFired = false; // if true, halts processing of later pendingCommands
+ bool hasFired = false; // if true, halts processing of later pendingCommands}
+ asyncKeyValueCallback watchUpdateCb; // May be null.
void setGame(Game g) {
this.game = g;
@@ -101,5 +102,4 @@
void addToLogCb(List<GameCommand> log, GameCommand newCommand);
List<GameCommand> updateLogCb(
List<GameCommand> current, List<GameCommand> other, int mismatchIndex);
- void close();
}
diff --git a/lib/logic/hearts/hearts_log.part.dart b/lib/logic/hearts/hearts_log.part.dart
index c6ef13e..22849c5 100644
--- a/lib/logic/hearts/hearts_log.part.dart
+++ b/lib/logic/hearts/hearts_log.part.dart
@@ -19,6 +19,8 @@
logWriter = new LogWriter(handleSyncUpdate, [0, 1, 2, 3]);
logWriter.associatedUser = this.game.playerNumber;
logWriter.logPrefix = "${game.gameID}/log";
+
+ watchUpdateCb = logWriter.onChange;
}
void handleSyncUpdate(String key, String cmd) {
@@ -45,9 +47,4 @@
assert(false);
return current;
}
-
- @override
- void close() {
- logWriter.close();
- }
}
diff --git a/lib/logic/proto/proto_log.part.dart b/lib/logic/proto/proto_log.part.dart
index 1450f6e..5ff7c70 100644
--- a/lib/logic/proto/proto_log.part.dart
+++ b/lib/logic/proto/proto_log.part.dart
@@ -16,7 +16,4 @@
assert(false); // This game can't have conflicts.
return current;
}
-
- @override
- void close() {}
}
diff --git a/lib/logic/solitaire/solitaire_log.part.dart b/lib/logic/solitaire/solitaire_log.part.dart
index 7a1d3ce..579c687 100644
--- a/lib/logic/solitaire/solitaire_log.part.dart
+++ b/lib/logic/solitaire/solitaire_log.part.dart
@@ -19,6 +19,8 @@
logWriter = new LogWriter(handleSyncUpdate, [0]);
logWriter.associatedUser = this.game.playerNumber;
logWriter.logPrefix = "${game.gameID}/log";
+
+ watchUpdateCb = logWriter.onChange;
}
void handleSyncUpdate(String key, String cmd) {
@@ -45,9 +47,4 @@
assert(false);
return current;
}
-
- @override
- void close() {
- logWriter.close();
- }
}
diff --git a/lib/src/mocks/log_writer.dart b/lib/src/mocks/log_writer.dart
index 19e6d58..bc8cc97 100644
--- a/lib/src/mocks/log_writer.dart
+++ b/lib/src/mocks/log_writer.dart
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+import 'dart:async';
import 'dart:convert' show JSON;
enum SimulLevel { TURN_BASED, INDEPENDENT, DEPENDENT }
@@ -26,6 +27,8 @@
Map<String, String> _data = new Map<String, String>();
+ Future onChange(String rowKey, String value, bool duringScan) async {}
+
void write(SimulLevel s, String value) {
assert(!inProposalMode);
@@ -120,6 +123,4 @@
}
return true;
}
-
- void close() {}
}
diff --git a/lib/src/mocks/settings_manager.dart b/lib/src/mocks/settings_manager.dart
index b767029..d3509cf 100644
--- a/lib/src/mocks/settings_manager.dart
+++ b/lib/src/mocks/settings_manager.dart
@@ -14,13 +14,15 @@
final util.keyValueCallback updateGamesCallback;
final util.keyValueCallback updatePlayerFoundCallback;
final util.keyValueCallback updateGameStatusCallback;
+ final util.asyncKeyValueCallback updateGameLogCallback;
SettingsManager(
settings_client.AppSettings _,
this.updateCallback,
this.updateGamesCallback,
this.updatePlayerFoundCallback,
- this.updateGameStatusCallback);
+ this.updateGameStatusCallback,
+ this.updateGameLogCallback);
Map<String, String> _data = new Map<String, String>();
@@ -89,4 +91,6 @@
Future<String> getGameSyncgroup(int gameID) async {
return new Future(() => null);
}
+
+ void quitGame() {}
}
diff --git a/lib/src/mocks/util.dart b/lib/src/mocks/util.dart
index 0413ea1..0096922 100644
--- a/lib/src/mocks/util.dart
+++ b/lib/src/mocks/util.dart
@@ -2,7 +2,10 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+import 'dart:async';
+
typedef void keyValueCallback(String key, String value);
+typedef Future asyncKeyValueCallback(String key, String value, bool duringScan);
String gameIDFromGameKey(String gameKey) {
return null;
diff --git a/lib/src/syncbase/log_writer.dart b/lib/src/syncbase/log_writer.dart
index 21eecc9..16a6dfc 100644
--- a/lib/src/syncbase/log_writer.dart
+++ b/lib/src/syncbase/log_writer.dart
@@ -43,10 +43,6 @@
// Affects read/write/watch locations of the log writer.
String logPrefix = ''; // This is usually set to <game_id>/log
- // An internal StreamSubscription that should be canceled once we are done
- // watching on this log.
- StreamSubscription<WatchChange> _watchSubscription;
-
// When a proposal is made (or received), this flag is set to true.
// Once a consensus has been reached, this is set to false again.
bool inProposalMode = false;
@@ -119,16 +115,9 @@
SyncbaseDatabase db = await _cc.createDatabase();
tb = await _cc.createTable(db, tbName);
-
- // Start to watch the stream.
- this._watchSubscription = await _cc.watchEverything(
- db, tbName, this.logPrefix, _onChange,
- sorter: (WatchChange a, WatchChange b) {
- return a.rowKey.compareTo(b.rowKey);
- });
}
- Future _onChange(String rowKey, String value, bool duringScan) async {
+ Future onChange(String rowKey, String value, bool duringScan) async {
String key = rowKey.replaceFirst("${this.logPrefix}/", "");
String timeStr = key.split("-")[0];
DateTime keyTime = _parseTime(timeStr);
@@ -148,13 +137,6 @@
}
}
- void close() {
- if (this._watchSubscription != null) {
- this._watchSubscription.cancel();
- this._watchSubscription = null;
- }
- }
-
Future write(SimulLevel s, String value) async {
util.log('LogWriter.write start');
await _prepareLog();
diff --git a/lib/src/syncbase/settings_manager.dart b/lib/src/syncbase/settings_manager.dart
index b6c8e31..b6e2acf 100644
--- a/lib/src/syncbase/settings_manager.dart
+++ b/lib/src/syncbase/settings_manager.dart
@@ -32,17 +32,22 @@
final util.keyValueCallback updateGamesCallback;
final util.keyValueCallback updatePlayerFoundCallback;
final util.keyValueCallback updateGameStatusCallback;
+ final util.asyncKeyValueCallback updateGameLogCallback;
final CroupierClient _cc;
sc.SyncbaseTable tb;
static const String _discoveryGameAdKey = "discovery-game-ad";
+ // The game subscription. Cancel when done listening.
+ StreamSubscription<sc.WatchChange> _gameSubscription;
+
SettingsManager(
settings_client.AppSettings appSettings,
this.updateSettingsCallback,
this.updateGamesCallback,
this.updatePlayerFoundCallback,
- this.updateGameStatusCallback)
+ this.updateGameStatusCallback,
+ this.updateGameLogCallback)
: _cc = new CroupierClient(appSettings);
Future _prepareSettingsTable() async {
@@ -143,6 +148,10 @@
if (this.updateGameStatusCallback != null) {
this.updateGameStatusCallback(key, value);
}
+ } else if (key.indexOf("/log") != -1) {
+ if (this.updateGameLogCallback != null) {
+ await this.updateGameLogCallback(key, value, duringScan);
+ }
}
}
@@ -152,9 +161,13 @@
sc.SyncbaseDatabase db = await _cc.createDatabase();
sc.SyncbaseTable gameTable = await _cc.createTable(db, util.tableNameGames);
- // Watch for the players in the game.
- await _cc.watchEverything(
- db, util.tableNameGames, util.syncgamePrefix(gameID), _onGameChange);
+ // Watch all the data in the game.
+ assert(_gameSubscription == null);
+ _gameSubscription = await _cc.watchEverything(
+ db, util.tableNameGames, util.syncgamePrefix(gameID), _onGameChange,
+ sorter: (sc.WatchChange a, sc.WatchChange b) {
+ return a.rowKey.compareTo(b.rowKey);
+ });
print("Now writing to some rows of ${gameID}");
// Start up the table and write yourself as player 0.
@@ -179,6 +192,13 @@
return gsd;
}
+ void quitGame() {
+ if (_gameSubscription != null) {
+ _gameSubscription.cancel();
+ _gameSubscription = null;
+ }
+ }
+
Future joinGameSyncgroup(String sgName, int gameID) async {
print("Now joining game syncgroup at ${sgName} and ${gameID}");
@@ -186,7 +206,7 @@
sc.SyncbaseTable gameTable = await _cc.createTable(db, util.tableNameGames);
// Watch for the players in the game.
- await _cc.watchEverything(
+ _gameSubscription = await _cc.watchEverything(
db, util.tableNameGames, util.syncgamePrefix(gameID), _onGameChange);
await _cc.joinSyncgroup(sgName);