From 328b8a7727e9475ef05b1756e887916149095ab1 Mon Sep 17 00:00:00 2001 From: Eric Seidel Date: Sun, 18 Feb 2024 16:11:57 -0800 Subject: [PATCH] feat: move BehaviorState into db. --- packages/cli/bin/clear_system_watchers.dart | 7 +- packages/cli/bin/deals_in_progress.dart | 5 +- packages/cli/bin/deals_nearby.dart | 2 +- packages/cli/bin/earning_per_ship.dart | 2 +- packages/cli/bin/fleet.dart | 2 +- packages/cli/bin/fleet_charters.dart | 4 +- packages/cli/bin/fleet_needed_mounts.dart | 2 +- packages/cli/bin/market_feeder.dart | 2 +- packages/cli/bin/system_to_chart_next.dart | 2 +- packages/cli/bin/system_watchers.dart | 2 +- packages/cli/lib/behavior/advance.dart | 10 +-- packages/cli/lib/behavior/trader.dart | 7 +- packages/cli/lib/cache/behavior_cache.dart | 73 +++++++------------ packages/cli/lib/cache/caches.dart | 2 +- packages/cli/lib/cache/json_store.dart | 51 ------------- packages/cli/test/behavior/advance_test.dart | 4 + .../test/behavior/central_command_test.dart | 44 +++++------ .../cli/test/cache/behavior_cache_test.dart | 61 +++------------- packages/cli/test/cache/caches_test.dart | 1 + packages/db/lib/db.dart | 47 +++++++----- packages/db/lib/src/behavior.dart | 24 ++++++ packages/types/lib/behavior.dart | 4 + packages/types/lib/src/symbol.dart | 6 ++ 23 files changed, 161 insertions(+), 203 deletions(-) delete mode 100644 packages/cli/lib/cache/json_store.dart diff --git a/packages/cli/bin/clear_system_watchers.dart b/packages/cli/bin/clear_system_watchers.dart index bfcc8085..57417b12 100644 --- a/packages/cli/bin/clear_system_watchers.dart +++ b/packages/cli/bin/clear_system_watchers.dart @@ -6,7 +6,8 @@ void main(List args) async { } Future command(FileSystem fs, ArgResults argResults) async { - final behaviorCache = BehaviorCache.load(fs); + final db = await defaultDatabase(); + final behaviorCache = await BehaviorCache.load(db); final shipSymbols = behaviorCache.states .where((s) => s.behavior == Behavior.systemWatcher) @@ -19,6 +20,8 @@ Future command(FileSystem fs, ArgResults argResults) async { } logger.info('Clearing ${shipSymbols.length} system watchers...'); for (final shipSymbol in shipSymbols) { - behaviorCache.deleteBehavior(shipSymbol); + await behaviorCache.deleteBehavior(shipSymbol); } + + await db.close(); } diff --git a/packages/cli/bin/deals_in_progress.dart b/packages/cli/bin/deals_in_progress.dart index 808b1c76..533f920a 100644 --- a/packages/cli/bin/deals_in_progress.dart +++ b/packages/cli/bin/deals_in_progress.dart @@ -16,7 +16,8 @@ String annotatedName(CostedDeal deal) { } Future cliMain(FileSystem fs, ArgResults argResults) async { - final behaviorCache = BehaviorCache.load(fs); + final db = await defaultDatabase(); + final behaviorCache = await BehaviorCache.load(db); final states = behaviorCache.states.where((state) => state.deal != null).toList(); @@ -99,6 +100,8 @@ Future cliMain(FileSystem fs, ArgResults argResults) async { '${minerHaulers.length} miner haulers: ${minerHaulers.join(', ')}', ); } + + await db.close(); } void main(List args) async { diff --git a/packages/cli/bin/deals_nearby.dart b/packages/cli/bin/deals_nearby.dart index f2edff55..f7bb6584 100644 --- a/packages/cli/bin/deals_nearby.dart +++ b/packages/cli/bin/deals_nearby.dart @@ -27,7 +27,7 @@ Future cliMain(FileSystem fs, ArgResults argResults) async { ); final marketPrices = await MarketPrices.load(db); - final behaviorCache = BehaviorCache.load(fs); + final behaviorCache = await BehaviorCache.load(db); final shipCache = ShipCache.load(fs)!; final agentCache = await AgentCache.load(db); final contractSnapshot = await ContractSnapshot.load(db); diff --git a/packages/cli/bin/earning_per_ship.dart b/packages/cli/bin/earning_per_ship.dart index 19c8d80b..7bac27ea 100644 --- a/packages/cli/bin/earning_per_ship.dart +++ b/packages/cli/bin/earning_per_ship.dart @@ -54,7 +54,7 @@ Future command(FileSystem fs, ArgResults argResults) async { final shipSymbols = (await db.uniqueShipSymbolsInTransactions()).toList() ..sort(); - final behaviorCache = BehaviorCache.load(fs); + final behaviorCache = await BehaviorCache.load(db); final shipCache = ShipCache.load(fs)!; final idleHaulers = idleHaulerSymbols(shipCache, behaviorCache); diff --git a/packages/cli/bin/fleet.dart b/packages/cli/bin/fleet.dart index 616e50aa..31394973 100644 --- a/packages/cli/bin/fleet.dart +++ b/packages/cli/bin/fleet.dart @@ -99,7 +99,7 @@ bool Function(Ship) filterFromArgs(List args) { Future command(FileSystem fs, ArgResults argResults) async { final db = await defaultDatabase(); final filter = filterFromArgs(argResults.rest); - final behaviorCache = BehaviorCache.load(fs); + final behaviorCache = await BehaviorCache.load(db); final shipCache = ShipCache.load(fs)!; logger.info('Fleet: ${describeShips(shipCache.ships)}'); diff --git a/packages/cli/bin/fleet_charters.dart b/packages/cli/bin/fleet_charters.dart index 6e0e03b4..d784fa54 100644 --- a/packages/cli/bin/fleet_charters.dart +++ b/packages/cli/bin/fleet_charters.dart @@ -44,7 +44,8 @@ String plural(int count, String singular, [String plural = 's']) { } Future command(FileSystem fs, ArgResults argResults) async { - final behaviorCache = BehaviorCache.load(fs); + final db = await defaultDatabase(); + final behaviorCache = await BehaviorCache.load(db); final charterStates = behaviorCache.states.where((s) => s.behavior == Behavior.charter); final systemsCache = SystemsCache.load(fs)!; @@ -66,4 +67,5 @@ Future command(FileSystem fs, ArgResults argResults) async { logger.info(' enroute to $destination $destinationType in $arrival'); } } + await db.close(); } diff --git a/packages/cli/bin/fleet_needed_mounts.dart b/packages/cli/bin/fleet_needed_mounts.dart index 82445c4f..60e42684 100644 --- a/packages/cli/bin/fleet_needed_mounts.dart +++ b/packages/cli/bin/fleet_needed_mounts.dart @@ -7,7 +7,7 @@ import 'package:cli/cli.dart'; Future command(FileSystem fs, ArgResults argResults) async { final db = await defaultDatabase(); final shipCache = ShipCache.load(fs)!; - final behaviorCache = BehaviorCache.load(fs); + final behaviorCache = await BehaviorCache.load(db); final marketPrices = await MarketPrices.load(db); final centralCommand = CentralCommand(behaviorCache: behaviorCache, shipCache: shipCache); diff --git a/packages/cli/bin/market_feeder.dart b/packages/cli/bin/market_feeder.dart index d55f65df..3c8683fc 100644 --- a/packages/cli/bin/market_feeder.dart +++ b/packages/cli/bin/market_feeder.dart @@ -19,7 +19,7 @@ Future command(FileSystem fs, ArgResults argResults) async { sellsFuel: defaultSellsFuel(marketListings), ); - final behaviorCache = BehaviorCache.load(fs); + final behaviorCache = await BehaviorCache.load(db); const shipType = ShipType.LIGHT_HAULER; final ship = staticCaches.shipyardShips[shipType]!; diff --git a/packages/cli/bin/system_to_chart_next.dart b/packages/cli/bin/system_to_chart_next.dart index 5b431c27..78d8cd31 100644 --- a/packages/cli/bin/system_to_chart_next.dart +++ b/packages/cli/bin/system_to_chart_next.dart @@ -20,7 +20,7 @@ Future command(FileSystem fs, ArgResults argResults) async { final systemConnectivity = await loadSystemConnectivity(db); final shipCache = ShipCache.load(fs)!; - final behaviorCache = BehaviorCache.load(fs); + final behaviorCache = await BehaviorCache.load(db); final centralCommand = CentralCommand( shipCache: shipCache, behaviorCache: behaviorCache, diff --git a/packages/cli/bin/system_watchers.dart b/packages/cli/bin/system_watchers.dart index cc5d3d12..80def323 100644 --- a/packages/cli/bin/system_watchers.dart +++ b/packages/cli/bin/system_watchers.dart @@ -48,7 +48,7 @@ Future command(FileSystem fs, ArgResults argResults) async { final marketListings = await MarketListingSnapshot.load(db); final systemsToWatch = marketListings.systemsWithAtLeastNMarkets(5); - final behaviorCache = BehaviorCache.load(fs); + final behaviorCache = await BehaviorCache.load(db); final systemWatcherStates = behaviorCache.states.where((s) => s.behavior == Behavior.systemWatcher); final systemsCache = SystemsCache.load(fs)!; diff --git a/packages/cli/lib/behavior/advance.dart b/packages/cli/lib/behavior/advance.dart index 2808b274..e09cf8c9 100644 --- a/packages/cli/lib/behavior/advance.dart +++ b/packages/cli/lib/behavior/advance.dart @@ -94,7 +94,7 @@ Future advanceShipBehavior( ); } on JobException catch (e) { shipErr(ship, '$e'); - caches.behaviors.deleteBehavior(ship.shipSymbol); + await caches.behaviors.deleteBehavior(ship.shipSymbol); return null; } if (navResult.shouldReturn()) { @@ -115,14 +115,14 @@ Future advanceShipBehavior( ); if (state.isComplete) { // If the behavior is complete, clear it. - caches.behaviors.deleteBehavior(ship.shipSymbol); + await caches.behaviors.deleteBehavior(ship.shipSymbol); } else { // Otherwise update the behavior state. - caches.behaviors.setBehavior(ship.shipSymbol, state); + await caches.behaviors.setBehavior(ship.shipSymbol, state); } return waitUntil; } on JobException catch (error) { - caches.behaviors.disableBehaviorForShip( + await caches.behaviors.disableBehaviorForShip( ship, error.message, error.timeout, @@ -131,7 +131,7 @@ Future advanceShipBehavior( if (!isInsufficientCreditsException(e)) { rethrow; } - caches.behaviors.disableBehaviorForShip( + await caches.behaviors.disableBehaviorForShip( ship, 'Insufficient credits: ${e.message}', const Duration(minutes: 10), diff --git a/packages/cli/lib/behavior/trader.dart b/packages/cli/lib/behavior/trader.dart index 7cc9bbaf..683f5a14 100644 --- a/packages/cli/lib/behavior/trader.dart +++ b/packages/cli/lib/behavior/trader.dart @@ -562,7 +562,12 @@ Future acceptContractsIfNeeded( final contracts = contractSnapshot.activeContracts; if (contracts.isEmpty) { final contract = await negotiateContractAndLog( - db, api, ship, shipCache, contractSnapshot); + db, + api, + ship, + shipCache, + contractSnapshot, + ); shipInfo(ship, describeExpectedContractProfit(marketPrices, contract)); return null; } diff --git a/packages/cli/lib/cache/behavior_cache.dart b/packages/cli/lib/cache/behavior_cache.dart index 465cdedb..b852445a 100644 --- a/packages/cli/lib/cache/behavior_cache.dart +++ b/packages/cli/lib/cache/behavior_cache.dart @@ -1,12 +1,9 @@ -import 'package:cli/cache/json_store.dart'; import 'package:cli/logger.dart'; import 'package:cli/printing.dart'; -import 'package:file/file.dart'; +import 'package:db/db.dart'; import 'package:meta/meta.dart'; import 'package:types/types.dart'; -typedef _Record = Map; - @immutable class _ShipTimeout { const _ShipTimeout(this.shipSymbol, this.behavior, this.timeout); @@ -17,49 +14,26 @@ class _ShipTimeout { } /// A class to manage the behavior cache. -class BehaviorCache extends JsonStore<_Record> { +class BehaviorCache { /// Create a new behavior cache. - BehaviorCache( - super.stateByShipSymbol, { - required super.fs, - super.path = defaultPath, - }) : super( - recordToJson: (_Record r) => r.map( - (key, value) => MapEntry( - key.toJson(), - value.toJson(), - ), - ), - ); + BehaviorCache(Iterable stateByShipSymbol, Database db) + : _stateByShipSymbol = Map.fromEntries( + stateByShipSymbol.map((state) => MapEntry(state.shipSymbol, state)), + ), + _db = db; /// Load the cache from a file. - factory BehaviorCache.load( - FileSystem fs, { - String path = defaultPath, - }) { - final record = JsonStore.loadRecord<_Record>( - fs, - path, - (Map j) => j.map( - (key, value) => MapEntry( - ShipSymbol.fromJson(key), - BehaviorState.fromJson(value as Map), - ), - ), - ) ?? - {}; - return BehaviorCache(record, fs: fs, path: path); + static Future load(Database db) async { + final states = await db.allBehaviorStates(); + return BehaviorCache(states, db); } + final Database _db; + final Map _stateByShipSymbol; + /// Used for temporarily disabling a ship, not persisted. final List<_ShipTimeout> _shipTimeouts = []; - /// The default path to the cache file. - static const String defaultPath = 'data/behaviors.json'; - - /// The behavior state for each ship. - Map get _stateByShipSymbol => record; - /// Get the list of all behavior states. List get states => _stateByShipSymbol.values.toList(); @@ -68,15 +42,18 @@ class BehaviorCache extends JsonStore<_Record> { _stateByShipSymbol[shipSymbol]; /// Delete the behavior state for the given ship. - void deleteBehavior(ShipSymbol shipSymbol) { + Future deleteBehavior(ShipSymbol shipSymbol) async { + await _db.deleteBehaviorState(shipSymbol); _stateByShipSymbol.remove(shipSymbol); - save(); } /// Set the behavior state for the given ship. - void setBehavior(ShipSymbol shipSymbol, BehaviorState behaviorState) { + Future setBehavior( + ShipSymbol shipSymbol, + BehaviorState behaviorState, + ) async { + await _db.setBehaviorState(behaviorState); _stateByShipSymbol[shipSymbol] = behaviorState; - save(); } /// Check if the given behavior is disabled for the given ship. @@ -99,7 +76,11 @@ class BehaviorCache extends JsonStore<_Record> { } /// Disable the given behavior for [ship] for [duration]. - void disableBehaviorForShip(Ship ship, String why, Duration duration) { + Future disableBehaviorForShip( + Ship ship, + String why, + Duration duration, + ) async { final shipSymbol = ship.shipSymbol; final currentState = getBehavior(shipSymbol); final behavior = currentState?.behavior; @@ -114,7 +95,7 @@ class BehaviorCache extends JsonStore<_Record> { ); if (currentState == null || currentState.behavior == behavior) { - deleteBehavior(shipSymbol); + await deleteBehavior(shipSymbol); } else { shipInfo(ship, 'Not deleting ${currentState.behavior} for $shipSymbol.'); } @@ -134,7 +115,7 @@ class BehaviorCache extends JsonStore<_Record> { return currentState; } final newState = await ifAbsent(); - setBehavior(shipSymbol, newState); + await setBehavior(shipSymbol, newState); return newState; } } diff --git a/packages/cli/lib/cache/caches.dart b/packages/cli/lib/cache/caches.dart index 6e5b20db..312fabfc 100644 --- a/packages/cli/lib/cache/caches.dart +++ b/packages/cli/lib/cache/caches.dart @@ -152,7 +152,7 @@ class Caches { final markets = MarketCache(db, api, static.tradeGoods); // Intentionally force refresh contracts in case we've been offline. final contracts = await fetchContracts(db, api); - final behaviors = BehaviorCache.load(fs); + final behaviors = await BehaviorCache.load(db); final jumpGates = await JumpGateSnapshot.load(db); final constructionSnapshot = await ConstructionSnapshot.load(db); diff --git a/packages/cli/lib/cache/json_store.dart b/packages/cli/lib/cache/json_store.dart deleted file mode 100644 index e0272b17..00000000 --- a/packages/cli/lib/cache/json_store.dart +++ /dev/null @@ -1,51 +0,0 @@ -import 'dart:convert'; - -import 'package:file/file.dart'; -import 'package:meta/meta.dart'; - -/// A class to manage a file containing a json object. -/// The resulting file is valid json. -class JsonStore { - /// Create a new JsonStore. - JsonStore( - this.record, { - required FileSystem fs, - required String path, - required Map Function(Record) recordToJson, - }) : _fs = fs, - _path = path, - _toJson = recordToJson; - - /// The root object of the store. - @protected - Record record; - - final Map Function(Record) _toJson; - - final String _path; - - /// The file system to use. - final FileSystem _fs; - - /// Save record to a file. - void save() { - final file = _fs.file(_path)..createSync(recursive: true); - const encoder = JsonEncoder.withIndent(' '); - final prettyprint = encoder.convert(_toJson(record)); - file.writeAsStringSync(prettyprint); - } - - /// Load record from a file. - static Record? loadRecord( - FileSystem fs, - String path, - Record Function(Map) recordFromJson, - ) { - final file = fs.file(path); - if (file.existsSync()) { - final contents = file.readAsStringSync(); - return recordFromJson(jsonDecode(contents) as Map); - } - return null; - } -} diff --git a/packages/cli/test/behavior/advance_test.dart b/packages/cli/test/behavior/advance_test.dart index 54860ee2..f7f5643c 100644 --- a/packages/cli/test/behavior/advance_test.dart +++ b/packages/cli/test/behavior/advance_test.dart @@ -44,6 +44,10 @@ void main() { when(() => caches.behaviors.putIfAbsent(shipSymbol, any())) .thenAnswer((_) => Future.value(behaviorState)); final logger = _MockLogger(); + + when(() => caches.behaviors.deleteBehavior(shipSymbol)) + .thenAnswer((_) async {}); + final waitUntil = await runWithLogger( logger, () => advanceShipBehavior( diff --git a/packages/cli/test/behavior/central_command_test.dart b/packages/cli/test/behavior/central_command_test.dart index e4687377..d9cef3bf 100644 --- a/packages/cli/test/behavior/central_command_test.dart +++ b/packages/cli/test/behavior/central_command_test.dart @@ -3,7 +3,6 @@ import 'package:cli/cache/caches.dart'; import 'package:cli/config.dart'; import 'package:cli/logger.dart'; import 'package:db/db.dart'; -import 'package:file/memory.dart'; import 'package:mocktail/mocktail.dart'; import 'package:test/test.dart'; import 'package:types/types.dart'; @@ -41,7 +40,7 @@ class _MockShipyardPrices extends Mock implements ShipyardPrices {} class _MockShipyardShipCache extends Mock implements ShipyardShipCache {} void main() { - test('CentralCommand.otherCharterSystems', () { + test('CentralCommand.otherCharterSystems', () async { RoutePlan fakeJump(WaypointSymbol start, WaypointSymbol end) { return RoutePlan( fuelCapacity: 10, @@ -58,24 +57,29 @@ void main() { ); } - final fs = MemoryFileSystem.test(); - final behaviorCache = BehaviorCache.load(fs); + final db = _MockDatabase(); + registerFallbackValue(BehaviorState.fallbackValue()); + registerFallbackValue(const ShipSymbol.fallbackValue()); + when(() => db.setBehaviorState(any())).thenAnswer((_) async {}); + when(() => db.deleteBehaviorState(any())).thenAnswer((_) async {}); + + final behaviorCache = BehaviorCache([], db); final shipCache = _MockShipCache(); final centralCommand = CentralCommand(behaviorCache: behaviorCache, shipCache: shipCache); - void setupShip({ + Future setupShip({ required ShipSymbol shipSymbol, required WaypointSymbol start, required WaypointSymbol end, - }) { + }) async { final ship = _MockShip(); final shipNav = _MockShipNav(); when(() => ship.symbol).thenReturn(shipSymbol.symbol); when(() => shipNav.waypointSymbol).thenReturn(start.waypoint); when(() => shipNav.systemSymbol).thenReturn(start.systemString); when(() => ship.nav).thenReturn(shipNav); - behaviorCache.setBehavior( + await behaviorCache.setBehavior( shipSymbol, BehaviorState( shipSymbol, @@ -90,13 +94,13 @@ void main() { final shipASymbol = ShipSymbol.fromString('X-A'); final aStart = WaypointSymbol.fromString('S-A-A'); final aEnd = WaypointSymbol.fromString('S-A-W'); - setupShip(shipSymbol: shipASymbol, start: aStart, end: aEnd); + await setupShip(shipSymbol: shipASymbol, start: aStart, end: aEnd); /// Sets up a ship X-B with a route from S-C-A to S-B-W. final shipBSymbol = ShipSymbol.fromString('X-B'); final bStart = WaypointSymbol.fromString('S-C-A'); final bEnd = WaypointSymbol.fromString('S-B-W'); - setupShip(shipSymbol: shipBSymbol, start: bStart, end: bEnd); + await setupShip(shipSymbol: shipBSymbol, start: bStart, end: bEnd); // Test that from S-A-A we avoid S-A-W and S-B-W. final otherSystems = @@ -112,7 +116,7 @@ void main() { expect(otherSystems2, [bStart.system]); // From nav.waypointSymbol // Remove shipB and we should avoid nothing. - behaviorCache.deleteBehavior(shipBSymbol); + await behaviorCache.deleteBehavior(shipBSymbol); final otherSystems3 = centralCommand.otherCharterSystems(shipASymbol).toList(); expect(otherSystems3, []); @@ -120,7 +124,7 @@ void main() { final shipCSymbol = ShipSymbol.fromString('X-C'); final cStart = WaypointSymbol.fromString('S-D-A'); final cEnd = WaypointSymbol.fromString('S-E-W'); - setupShip(shipSymbol: shipCSymbol, start: cStart, end: cEnd); + await setupShip(shipSymbol: shipCSymbol, start: cStart, end: cEnd); final otherSystems4 = centralCommand.otherCharterSystems(shipASymbol).toList(); expect( @@ -304,17 +308,15 @@ void main() { test('dealsInProgress smoke test', () { final deal = _MockCostedDeal(); + final db = _MockDatabase(); final cache = BehaviorCache( - { - ShipSymbol.fromString('X-A'): - BehaviorState(ShipSymbol.fromString('X-A'), Behavior.miner), - ShipSymbol.fromString('X-B'): - BehaviorState(ShipSymbol.fromString('X-B'), Behavior.trader), - ShipSymbol.fromString('X-C'): - BehaviorState(ShipSymbol.fromString('X-C'), Behavior.trader) - ..deal = deal, - }, - fs: MemoryFileSystem.test(), + [ + BehaviorState(ShipSymbol.fromString('X-A'), Behavior.miner), + BehaviorState(ShipSymbol.fromString('X-B'), Behavior.trader), + BehaviorState(ShipSymbol.fromString('X-C'), Behavior.trader) + ..deal = deal, + ], + db, ); final deals = cache.dealsInProgress(); expect(deals.length, 1); diff --git a/packages/cli/test/cache/behavior_cache_test.dart b/packages/cli/test/cache/behavior_cache_test.dart index 2389a1ba..7feb7601 100644 --- a/packages/cli/test/cache/behavior_cache_test.dart +++ b/packages/cli/test/cache/behavior_cache_test.dart @@ -1,64 +1,25 @@ import 'package:cli/cache/behavior_cache.dart'; import 'package:cli/logger.dart'; -import 'package:file/memory.dart'; +import 'package:db/db.dart'; import 'package:mocktail/mocktail.dart'; import 'package:test/test.dart'; import 'package:types/types.dart'; +class _MockDatabase extends Mock implements Database {} + class _MockLogger extends Mock implements Logger {} class _MockShip extends Mock implements Ship {} void main() { - test('BehaviorCache roundtrip', () async { - final fs = MemoryFileSystem.test(); - const shipSymbol = ShipSymbol('S', 1); - final state = BehaviorState(shipSymbol, Behavior.buyShip); - final now = DateTime(2021); - final route = RoutePlan( - fuelCapacity: 100, - shipSpeed: 20, - actions: [ - RouteAction( - startSymbol: WaypointSymbol.fromString('S-E-A'), - endSymbol: WaypointSymbol.fromString('S-E-B'), - type: RouteActionType.navCruise, - seconds: 30, - fuelUsed: 10, - ), - ], - ); - state.routePlan = route; - final deal = Deal.test( - sourceSymbol: WaypointSymbol.fromString('S-A-B'), - destinationSymbol: WaypointSymbol.fromString('S-A-C'), - tradeSymbol: TradeSymbol.FUEL, - purchasePrice: 1, - sellPrice: 2, - ); - state.deal = CostedDeal( - deal: deal, - transactions: [], - startTime: now, - route: route, - cargoSize: 100, - costPerFuelUnit: 100, - costPerAntimatterUnit: 10000, - ); - final stateByShipSymbol = { - shipSymbol: state, - }; - BehaviorCache(stateByShipSymbol, fs: fs).save(); - final loaded = BehaviorCache.load(fs); - expect( - loaded.getBehavior(shipSymbol)!.behavior, - stateByShipSymbol[shipSymbol]!.behavior, - ); - }); - test('BehaviorCache.isDisabledForShip', () async { - final fs = MemoryFileSystem.test(); - final behaviorCache = BehaviorCache.load(fs); + final db = _MockDatabase(); + registerFallbackValue(BehaviorState.fallbackValue()); + registerFallbackValue(const ShipSymbol.fallbackValue()); + when(() => db.setBehaviorState(any())).thenAnswer((_) async {}); + when(() => db.deleteBehaviorState(any())).thenAnswer((_) async {}); + + final behaviorCache = BehaviorCache([], db); final ship = _MockShip(); const shipSymbol = ShipSymbol('S', 1); when(() => ship.symbol).thenReturn(shipSymbol.symbol); @@ -67,7 +28,7 @@ void main() { false, ); - behaviorCache.setBehavior( + await behaviorCache.setBehavior( shipSymbol, BehaviorState(shipSymbol, Behavior.trader), ); diff --git a/packages/cli/test/cache/caches_test.dart b/packages/cli/test/cache/caches_test.dart index 45f44c95..bf0cccae 100644 --- a/packages/cli/test/cache/caches_test.dart +++ b/packages/cli/test/cache/caches_test.dart @@ -75,6 +75,7 @@ void main() { when(db.allShipyardListings).thenAnswer((_) async => []); when(db.allShipyardPrices).thenAnswer((_) async => []); when(db.allJumpGates).thenAnswer((_) async => []); + when(db.allBehaviorStates).thenAnswer((_) async => []); final fs = MemoryFileSystem.test(); fs.file(SystemsCache.defaultCacheFilePath).createSync(recursive: true); diff --git a/packages/db/lib/db.dart b/packages/db/lib/db.dart index 858b7657..1155a4fd 100644 --- a/packages/db/lib/db.dart +++ b/packages/db/lib/db.dart @@ -1,5 +1,6 @@ import 'package:db/config.dart'; import 'package:db/src/agent.dart'; +import 'package:db/src/behavior.dart'; import 'package:db/src/chart.dart'; import 'package:db/src/construction.dart'; import 'package:db/src/contract.dart'; @@ -75,7 +76,7 @@ class Database { /// Insert one record using the provided query. @protected - Future insertOne(Query query) { + Future execute(Query query) { return connection.execute( query.fmtString, parameters: query.parameters, @@ -117,12 +118,12 @@ class Database { /// Insert a transaction into the database. Future insertTransaction(Transaction transaction) async { - await insertOne(insertTransactionQuery(transaction)); + await execute(insertTransactionQuery(transaction)); } /// Insert a survey into the database. Future insertSurvey(HistoricalSurvey survey) async { - await insertOne(insertSurveyQuery(survey)); + await execute(insertSurveyQuery(survey)); } /// Return the most recent surveys. @@ -176,7 +177,7 @@ class Database { /// Insert an extraction into the database. Future insertExtraction(ExtractionRecord extraction) async => - insertOne(insertExtractionQuery(extraction)); + execute(insertExtractionQuery(extraction)); /// Get a construction record from the database. Future getConstruction( @@ -194,7 +195,7 @@ class Database { /// Insert a construction record into the database. Future upsertConstruction(ConstructionRecord record) async => - insertOne(upsertConstructionQuery(record)); + execute(upsertConstructionQuery(record)); /// Return all charting records. Future> allChartingRecords() async => @@ -202,7 +203,7 @@ class Database { /// Insert a charting record into the database. Future upsertChartingRecord(ChartingRecord record) async => - insertOne(upsertChartingRecordQuery(record)); + execute(upsertChartingRecordQuery(record)); /// Get a charting record from the database. Future getChartingRecord( @@ -248,7 +249,7 @@ class Database { /// Insert the given response into the database. Future insertResponse(ResponseRecord response) async { - await insertOne(insertResponseQuery(response)); + await execute(insertResponseQuery(response)); } /// Get the response with the given id. @@ -265,8 +266,7 @@ class Database { /// Update the given agent in the database. Future upsertAgent(Agent agent) async { - final query = upsertAgentQuery(agent); - await insertOne(query); + await execute(upsertAgentQuery(agent)); } /// Get the market listing for the given symbol. @@ -286,8 +286,7 @@ class Database { /// Update the given market listing in the database. Future upsertMarketListing(MarketListing listing) async { - final query = upsertMarketListingQuery(listing); - await insertOne(query); + await execute(upsertMarketListingQuery(listing)); } /// Get the shipyard listing for the given symbol. @@ -307,8 +306,7 @@ class Database { /// Update the given shipyard listing in the database. Future upsertShipyardListing(ShipyardListing listing) async { - final query = upsertShipyardListingQuery(listing); - await insertOne(query); + await execute(upsertShipyardListingQuery(listing)); } /// Get unique ship symbols from the transaction table. @@ -355,7 +353,7 @@ class Database { /// Add a market price to the database. Future upsertMarketPrice(MarketPrice price) async { - await insertOne(upsertMarketPriceQuery(price)); + await execute(upsertMarketPriceQuery(price)); } /// Get all shipyard prices from the database. @@ -365,7 +363,7 @@ class Database { /// Add a shipyard price to the database. Future upsertShipyardPrice(ShipyardPrice price) async { - await insertOne(upsertShipyardPriceQuery(price)); + await execute(upsertShipyardPriceQuery(price)); } /// Get all jump gates from the database. @@ -375,7 +373,7 @@ class Database { /// Add a jump gate to the database. Future upsertJumpGate(JumpGate jumpGate) async { - await insertOne(upsertJumpGateQuery(jumpGate)); + await execute(upsertJumpGateQuery(jumpGate)); } /// Get the jump gate for the given waypoint symbol. @@ -391,6 +389,21 @@ class Database { /// Upsert a contract into the database. Future upsertContract(Contract contract) async { - await insertOne(upsertContractQuery(contract)); + await execute(upsertContractQuery(contract)); + } + + /// Get all behavior states. + Future> allBehaviorStates() async { + return queryMany(allBehaviorStatesQuery(), behaviorStateFromColumnMap); + } + + /// Get a behavior state by symbol. + Future setBehaviorState(BehaviorState behaviorState) async { + await execute(upsertBehaviorStateQuery(behaviorState)); + } + + /// Delete a behavior state. + Future deleteBehaviorState(ShipSymbol shipSymbol) async { + await execute(deleteBehaviorStateQuery(shipSymbol)); } } diff --git a/packages/db/lib/src/behavior.dart b/packages/db/lib/src/behavior.dart index 37cdcc10..92121b52 100644 --- a/packages/db/lib/src/behavior.dart +++ b/packages/db/lib/src/behavior.dart @@ -1,6 +1,9 @@ import 'package:db/src/query.dart'; import 'package:types/types.dart'; +/// Query all behavior states. +Query allBehaviorStatesQuery() => const Query('SELECT * FROM behavior_'); + /// Query a ship behavior state by symbol. Query behaviorBySymbolQuery(ShipSymbol shipSymbol) => Query( 'SELECT * FROM behavior_ WHERE ship_symbol = @ship_symbol', @@ -9,6 +12,27 @@ Query behaviorBySymbolQuery(ShipSymbol shipSymbol) => Query( }, ); +/// Query to insert or update a behavior state. +Query upsertBehaviorStateQuery(BehaviorState state) => Query( + ''' + INSERT INTO behavior_ (ship_symbol, json) + VALUES (@ship_symbol, @json) + ON CONFLICT (ship_symbol) DO UPDATE SET json = @json + ''', + parameters: { + 'ship_symbol': state.shipSymbol.toJson(), + 'json': state.toJson(), + }, + ); + +/// Query to delete a behavior state. +Query deleteBehaviorStateQuery(ShipSymbol shipSymbol) => Query( + 'DELETE FROM behavior_ WHERE ship_symbol = @ship_symbol', + parameters: { + 'ship_symbol': shipSymbol.toJson(), + }, + ); + /// Convert a BehaviorState to a column map. Map behaviorStateToColumnMap(BehaviorState state) => { 'ship_symbol': state.shipSymbol.toJson(), diff --git a/packages/types/lib/behavior.dart b/packages/types/lib/behavior.dart index f87ce8d7..627c8b67 100644 --- a/packages/types/lib/behavior.dart +++ b/packages/types/lib/behavior.dart @@ -118,6 +118,10 @@ class BehaviorState { this.jobIndex = 0, }) : isComplete = false; + /// Create a new behavior state from a fallback value. + @visibleForTesting + BehaviorState.fallbackValue() : this(const ShipSymbol('S', 1), Behavior.idle); + /// Create a new behavior state from JSON. factory BehaviorState.fromJson(Map json) { final behavior = Behavior.fromJson(json['behavior'] as String); diff --git a/packages/types/lib/src/symbol.dart b/packages/types/lib/src/symbol.dart index 6defaf14..3a00abf4 100644 --- a/packages/types/lib/src/symbol.dart +++ b/packages/types/lib/src/symbol.dart @@ -153,6 +153,12 @@ class ShipSymbol extends Equatable implements Comparable { /// The number part is given in decimal, but will be represented in hex. const ShipSymbol(this.agentName, this.number); + /// Create a dummy ShipSymbol for testing. + @visibleForTesting + const ShipSymbol.fallbackValue() + : agentName = 'S', + number = 0; + /// Create a ShipSymbol from a string. ShipSymbol.fromString(String symbol) : agentName = _parseAgentName(symbol),