croupier: Modify the Croupier Schema
This makes way for the next step in game creation.
We need a procedure where we have space to invite players to a game.
Upon accepting those players must also form a new syncgroup for the
game. These two actions are highly related and cross the settings and
games tables. Thus, room must be made (prefix-shifting/table-combining),
which is what has occurred in this CL.
The game log now occurs at games/<game-id>/log -> all of the log data
The settings data now is all in the same table.
settings/personal holds the user's id
settings/users/<user_id>/settings holds the user's JSON settings.
Further, we are a little saner about keeping track of the player's userID.
Space has been made since:
games/<game-id> -> other stuff can be placed here for game formation
settings/users/<user_id> -> other stuff can be placed here for invitations
Change-Id: I5ffe78deb7ad9a126d993c8b84ae4364edda0f20
diff --git a/lib/logic/hearts/hearts_log.part.dart b/lib/logic/hearts/hearts_log.part.dart
index 9b92f2d..52685e4 100644
--- a/lib/logic/hearts/hearts_log.part.dart
+++ b/lib/logic/hearts/hearts_log.part.dart
@@ -8,7 +8,8 @@
LogWriter logWriter;
HeartsLog() {
- logWriter = new LogWriter(handleSyncUpdate, [0, 1, 2, 3]);
+ // TODO(alexfandrianto): The Game ID needs to be part of this constructor.
+ logWriter = new LogWriter(handleSyncUpdate, [0, 1, 2, 3], "<game_id>/log");
}
@override
diff --git a/lib/logic/solitaire/solitaire_log.part.dart b/lib/logic/solitaire/solitaire_log.part.dart
index 5551d31..973941c 100644
--- a/lib/logic/solitaire/solitaire_log.part.dart
+++ b/lib/logic/solitaire/solitaire_log.part.dart
@@ -8,7 +8,8 @@
LogWriter logWriter;
SolitaireLog() {
- logWriter = new LogWriter(handleSyncUpdate, [0]);
+ // TODO(alexfandrianto): The Game ID needs to be part of this constructor.
+ logWriter = new LogWriter(handleSyncUpdate, [0], "<game_id>/log");
}
@override
diff --git a/lib/src/mocks/log_writer.dart b/lib/src/mocks/log_writer.dart
index e5e753a..c48fc1c 100644
--- a/lib/src/mocks/log_writer.dart
+++ b/lib/src/mocks/log_writer.dart
@@ -11,11 +11,12 @@
class LogWriter {
final updateCallbackT updateCallback;
final List<int> users;
+ final String logPrefix; // This can be completely ignored.
bool inProposalMode = false;
int associatedUser;
- LogWriter(this.updateCallback, this.users);
+ LogWriter(this.updateCallback, this.users, this.logPrefix);
Map<String, String> _data = new Map<String, String>();
diff --git a/lib/src/syncbase/log_writer.dart b/lib/src/syncbase/log_writer.dart
index eefaef9..4940fe8 100644
--- a/lib/src/syncbase/log_writer.dart
+++ b/lib/src/syncbase/log_writer.dart
@@ -29,14 +29,28 @@
enum SimulLevel { TURN_BASED, INDEPENDENT, DEPENDENT }
class LogWriter {
+ // This callback is called on each watch update, passing the key and value.
final util.updateCallbackT updateCallback;
+
+ // The users that we should look for when coming to a proposal consensus.
final List<int> users;
+
+ // The CroupierClient manages the creation of tables/dbs/syncgroups.
final CroupierClient _cc;
+ // Affects read/write/watch locations of the log writer.
+ final String logPrefix;
+
+ // An internal boolean that should be set to true when watching and reset to
+ // false once watch should be turned off.
bool _watching = false;
+ // 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;
Map<String, String> proposalsKnown; // Only updated via watch.
+
+ // The associated user helps in the production of unique keys.
int _associatedUser;
int get associatedUser => _associatedUser;
void set associatedUser(int other) {
@@ -45,25 +59,28 @@
_associatedUser = other;
}
- LogWriter(this.updateCallback, this.users) : _cc = new CroupierClient() {
+ // This holds a reference to the syncbase table we're writing to.
+ SyncbaseTable tb;
+ static final String tbName = util.tableNameGames;
+
+ // The LogWriter takes a callback for watch updates, the list of users, and
+ // the logPrefix to write at on table.
+ LogWriter(this.updateCallback, this.users, this.logPrefix)
+ : _cc = new CroupierClient() {
_prepareLog();
}
- int seq = 0;
- SyncbaseTable tb;
- String sendMsg, recvMsg, putStr, getStr;
-
Future _prepareLog() async {
if (tb != null) {
return; // Then we're already prepared.
}
SyncbaseNoSqlDatabase db = await _cc.createDatabase();
- tb = await _cc.createTable(db, util.tableNameLog);
+ tb = await _cc.createTable(db, tbName);
// Start to watch the stream.
Stream<WatchChange> watchStream =
- db.watch(util.tableNameLog, '', await db.getResumeMarker());
+ db.watch(tbName, this.logPrefix, await db.getResumeMarker());
_startWatch(watchStream); // Don't wait for this future.
}
@@ -81,10 +98,10 @@
break;
}
- assert(wc.tableName == util.tableNameLog);
+ assert(wc.tableName == tbName);
util.log('Watch Key: ${wc.rowKey}');
util.log('Watch Value ${UTF8.decode(wc.valueBytes)}');
- String key = wc.rowKey;
+ String key = wc.rowKey.replaceFirst("${this.logPrefix}/", "");
String value;
switch (wc.changeType) {
case WatchChangeTypes.put:
@@ -145,14 +162,14 @@
// Helper that writes data to the "store" and calls the update callback.
Future _writeData(String key, String value) async {
- var row = tb.row(key);
+ var row = tb.row("${this.logPrefix}/${key}");
await row.put(UTF8.encode(value));
}
/*
// _readData could be helpful eventually, but it's not needed yet.
Future<String> _readData(String key) async {
- var row = tb.row(key);
+ var row = tb.row("${this.logPrefix}/${key}");
if (!(await row.exists())) {
print("${key} did not exist");
return null;
@@ -226,7 +243,7 @@
}
String _proposalKey(int user) {
- return "proposal${user}";
+ return "proposal/${user}";
}
Future<bool> _checkIsProposalDone() async {
diff --git a/lib/src/syncbase/settings_manager.dart b/lib/src/syncbase/settings_manager.dart
index 2dea144..367ee76 100644
--- a/lib/src/syncbase/settings_manager.dart
+++ b/lib/src/syncbase/settings_manager.dart
@@ -24,55 +24,52 @@
import 'package:discovery/discovery.dart' as discovery;
import 'package:syncbase/syncbase_client.dart' as sc;
-//import 'package:syncbase/src/naming/util.dart' as naming;
class SettingsManager {
final util.updateCallbackT updateCallback;
final CroupierClient _cc;
-
- static final String discoverySettingsKey = "settings";
-
sc.SyncbaseTable tb;
- sc.SyncbaseTable tbUser;
+
+ static const String _discoverySettingsKey = "settings";
+ static const String _personalKey = "personal";
+ static const String _settingsWatchSyncPrefix = "users";
SettingsManager([this.updateCallback]) : _cc = new CroupierClient();
+ String _settingsDataKey(int userID) {
+ return "${_settingsWatchSyncPrefix}/${userID}/settings";
+ }
+
Future _prepareSettingsTable() async {
- if (tb != null && tbUser != null) {
+ if (tb != null) {
return; // Then we're already prepared.
}
sc.SyncbaseNoSqlDatabase db = await _cc.createDatabase();
tb = await _cc.createTable(db, util.tableNameSettings);
- tbUser = await _cc.createTable(db, util.tableNameSettingsUser);
// Start to watch the stream for the shared settings table.
- Stream<sc.WatchChange> watchStream =
- db.watch(util.tableNameSettings, '', await db.getResumeMarker());
+ Stream<sc.WatchChange> watchStream = db.watch(util.tableNameSettings,
+ _settingsWatchSyncPrefix, await db.getResumeMarker());
_startWatch(watchStream); // Don't wait for this future.
_loadSettings(tb); // Don't wait for this future.
}
// Guaranteed to be called when the program starts.
// If no Croupier Settings exist, then random ones are created.
- Future<String> load([int userID]) async {
+ Future<String> load() async {
util.log('SettingsManager.load');
await _prepareSettingsTable();
- String result;
+ int userID = await _getUserID();
if (userID == null) {
- result = await _tryReadData(tbUser, "settings");
- } else {
- result = await _tryReadData(tb, "${userID}");
- }
- if (result == null) {
CroupierSettings settings = new CroupierSettings.random();
String jsonStr = settings.toJSONString();
await this.save(settings.userID, jsonStr);
return jsonStr;
+ } else {
+ return await _tryReadData(tb, this._settingsDataKey(userID));
}
-
- return result;
}
Future<String> _tryReadData(sc.SyncbaseTable st, String rowkey) async {
@@ -84,14 +81,15 @@
return UTF8.decode(await row.get());
}
- // Since only the current user is allowed to save, we should also save to the
- // user's personal settings as well.
+ // Note: only the current user is allowed to save settings.
+ // This means we can also save their user id.
+ // All other settings will be synced instead.
Future save(int userID, String jsonString) async {
util.log('SettingsManager.save');
await _prepareSettingsTable();
- await tbUser.row("settings").put(UTF8.encode(jsonString));
- await tb.row("${userID}").put(UTF8.encode(jsonString));
+ await tb.row(_personalKey).put(UTF8.encode("${userID}"));
+ await tb.row(this._settingsDataKey(userID)).put(UTF8.encode(jsonString));
}
// This watch method ensures that any changes are propagated to the caller.
@@ -125,18 +123,23 @@
// Best called after load(), to ensure that there are settings in the table.
Future createSyncgroup() async {
- CroupierSettings cs = await _getSettings();
+ int id = await _getUserID();
_cc.createSyncgroup(
_cc.makeSyncgroupName(await _syncSuffix()), util.tableNameSettings,
- prefix: "${cs.userID}");
+ prefix: this._settingsDataKey(id));
}
// When starting the settings manager, there may be settings already in the
// store. Make sure to load those.
Future _loadSettings(sc.SyncbaseTable tb) async {
- tb.scan(new sc.RowRange.prefix('')).forEach((sc.KeyValue kv) {
- this.updateCallback(kv.key, UTF8.decode(kv.value));
+ tb
+ .scan(new sc.RowRange.prefix(_settingsWatchSyncPrefix))
+ .forEach((sc.KeyValue kv) {
+ if (kv.key.endsWith("/settings")) {
+ // Then we can process the value as if it were settings data.
+ this.updateCallback(kv.key, UTF8.decode(kv.value));
+ }
});
}
@@ -147,36 +150,39 @@
// Someone who is creating a game should scan for players who wish to join.
Future scanSettings() async {
SettingsScanHandler ssh = new SettingsScanHandler(_cc);
- _cc.discoveryClient.scan(discoverySettingsKey, "CroupierSettings", ssh);
+ _cc.discoveryClient.scan(_discoverySettingsKey, "CroupierSettings", ssh);
}
void stopScanSettings() {
- _cc.discoveryClient.stopScan(discoverySettingsKey);
+ _cc.discoveryClient.stopScan(_discoverySettingsKey);
}
// Someone who wants to join a game should advertise their presence.
Future advertiseSettings() async {
String suffix = await _syncSuffix();
_cc.discoveryClient.advertise(
- discoverySettingsKey,
+ _discoverySettingsKey,
DiscoveryClient.serviceMaker(
interfaceName: "CroupierSettings",
addrs: <String>[_cc.makeSyncgroupName(suffix)]));
}
void stopAdvertiseSettings() {
- _cc.discoveryClient.stopAdvertise(discoverySettingsKey);
+ _cc.discoveryClient.stopAdvertise(_discoverySettingsKey);
}
- Future<CroupierSettings> _getSettings() async {
- String jsonSettings = await load();
- return new CroupierSettings.fromJSONString(jsonSettings);
+ Future<int> _getUserID() async {
+ String result = await _tryReadData(tb, _personalKey);
+ if (result == null) {
+ return null;
+ }
+ return int.parse(result);
}
Future<String> _syncSuffix() async {
- CroupierSettings cs = await _getSettings();
+ int id = await _getUserID();
- return "${util.sgSuffix}${cs.userID}";
+ return "${util.sgSuffix}${id}";
}
}
diff --git a/lib/src/syncbase/util.dart b/lib/src/syncbase/util.dart
index 02f1160..456e083 100644
--- a/lib/src/syncbase/util.dart
+++ b/lib/src/syncbase/util.dart
@@ -6,9 +6,8 @@
const appName = 'app';
const dbName = 'db';
-const tableNameLog = 'table';
+const tableNameGames = 'games';
const tableNameSettings = 'table_settings';
-const tableNameSettingsUser = 'table_settings_personal';
// TODO(alexfandrianto): This may need to be the global mount table with a
// proxy. Otherwise, it will be difficult for other users to run.