Skip to content

Commit

Permalink
refactor: add a visitor pattern to the supply chain
Browse files Browse the repository at this point in the history
The let me move the "Describe" code out of the supply chain file.
  • Loading branch information
eseidel committed Feb 25, 2024
1 parent 08d3b50 commit 8e1b062
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 85 deletions.
77 changes: 75 additions & 2 deletions packages/cli/bin/exports_supply_chain.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,79 @@ import 'package:cli/cache/caches.dart';
import 'package:cli/cli.dart';
import 'package:cli/supply_chain.dart';

// Returns the distance between two waypoints, or null if they are in different
// systems.
int? _distanceBetween(
SystemsCache systemsCache,
WaypointSymbol a,
WaypointSymbol b,
) {
final aWaypoint = systemsCache.waypoint(a);
final bWaypoint = systemsCache.waypoint(b);
if (aWaypoint.system != bWaypoint.system) {
return null;
}
return aWaypoint.distanceTo(bWaypoint).toInt();
}

String _describeMarket(WaypointSymbol waypointSymbol, MarketPrice? price) {
final name = waypointSymbol.waypointName;
if (price == null) {
return '$name (no market)';
}
return '$name (${price.supply}, ${price.activity})';
}

/// Walk the supply chain and print it.
class DescribingVisitor extends SupplyLinkVisitor {
/// Create a new describing visitor.
DescribingVisitor(this.systems, this.marketPrices);

/// The systems cache.
final SystemsCache systems;

/// The market prices.
final MarketPriceSnapshot marketPrices;

final _indent = ' ';

@override
void visitExtract(ExtractLink link, {required int depth}) {
final spaces = _indent * depth;
final from = link.waypointSymbol.waypointName;
logger.info('${spaces}Extract ${link.tradeSymbol} from $from');
}

@override
void visitShuttle(ShuttleLink link, {required int depth}) {
final spaces = _indent * depth;
final source = link.source.waypointSymbol;
final destination = link.destination;
final tradeSymbol = link.tradeSymbol;

final distance = _distanceBetween(systems, source, destination);
final sourcePrice = marketPrices.priceAt(source, tradeSymbol);
final destinationPrice = marketPrices.priceAt(destination, tradeSymbol);

logger.info(
'${spaces}Shuttle $tradeSymbol from '
'${_describeMarket(source, sourcePrice)} '
'to ${_describeMarket(destination, destinationPrice)} '
'distance = $distance',
);
}

@override
void visitManufacture(Manufacture link, {required int depth}) {
final spaces = _indent * depth;
final inputSymbols = link.inputs.keys.map((s) => s.toString()).join(', ');
logger.info(
'${spaces}Manufacture ${link.tradeSymbol} '
'at ${link.waypointSymbol} from $inputSymbols',
);
}
}

void source(
MarketListingSnapshot marketListings,
SystemsCache systemsCache,
Expand All @@ -20,8 +93,8 @@ void source(
logger.warn('No source for $tradeSymbol for $waypointSymbol');
return;
}
final ctx = DescribeContext(systemsCache, marketPrices);
action.describe(ctx);
final visitor = DescribingVisitor(systemsCache, marketPrices);
action.accept(visitor);
}

Future<void> command(FileSystem fs, Database db, ArgResults argResults) async {
Expand Down
106 changes: 23 additions & 83 deletions packages/cli/lib/supply_chain.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,16 @@ final _extractable = <TradeSymbol>{
..._siphonable,
};

/// Passed to describe when printing a supply chain.
class DescribeContext {
/// Create a new description context.
DescribeContext(this.systems, this.marketPrices);
/// A visitor for supply chain nodes.
abstract class SupplyLinkVisitor {
/// Visit an extraction node.
void visitExtract(ExtractLink link, {required int depth});

/// The systems cache.
final SystemsCache systems;
/// Visit a shuttle node.
void visitShuttle(ShuttleLink link, {required int depth});

/// The market prices.
final MarketPriceSnapshot marketPrices;
/// Visit a manufacture node.
void visitManufacture(Manufacture link, {required int depth});
}

/// A supply chain node.
Expand All @@ -46,8 +46,8 @@ abstract class SupplyLink {
/// The trade symbol being supplied.
final TradeSymbol tradeSymbol;

/// Describe the sub-graph of the supply chain.
void describe(DescribeContext ctx, {int indent = 0});
/// Does a depth-first traversal of the supply chain.
void accept(SupplyLinkVisitor visitor, {int depth = 0});
}

/// A supply chain node representing production
Expand All @@ -65,10 +65,8 @@ class ExtractLink extends ProduceLink {
ExtractLink(super.tradeSymbol, super.waypointSymbol);

@override
void describe(DescribeContext ctx, {int indent = 0}) {
final from = waypointSymbol.waypointName;
final spaces = ' ' * indent;
logger.info('${spaces}Extract $tradeSymbol from $from');
void accept(SupplyLinkVisitor visitor, {int depth = 0}) {
visitor.visitExtract(this, depth: depth);
}
}

Expand All @@ -85,64 +83,30 @@ class ShuttleLink extends SupplyLink {
final ProduceLink source;

@override
void describe(DescribeContext ctx, {int indent = 0}) {
final distance = distanceBetween(
ctx.systems,
source.waypointSymbol,
destination,
);

final sourcePrice = ctx.marketPrices.priceAt(
source.waypointSymbol,
tradeSymbol,
);

final destinationPrice = ctx.marketPrices.priceAt(
destination,
tradeSymbol,
);

final spaces = ' ' * indent;
logger.info(
'${spaces}Shuttle $tradeSymbol from '
'${describeMarket(source.waypointSymbol, sourcePrice)} '
'to ${describeMarket(destination, destinationPrice)} '
'distance = $distance',
);

source.describe(ctx, indent: indent + 1);
void accept(SupplyLinkVisitor visitor, {int depth = 0}) {
visitor.visitShuttle(this, depth: depth);
source.accept(visitor, depth: depth + 1);
}
}

/// A supply chain node representing a manufacture
class Manufacture extends ProduceLink {
/// Create a new manufacture node.
Manufacture(super.tradeSymbol, super.waypointSymbol, this.inputs);

// This could map String -> List if we wanted to support options.
/// The inputs to the manufacture.
final Map<TradeSymbol, ShuttleLink> inputs;

@override
void describe(DescribeContext ctx, {int indent = 0}) {
final inputSymbols = inputs.keys.map((s) => s.toString()).join(', ');
final spaces = ' ' * indent;
logger.info(
'${spaces}Manufacture $tradeSymbol at $waypointSymbol from $inputSymbols',
);
void accept(SupplyLinkVisitor visitor, {int depth = 0}) {
visitor.visitManufacture(this, depth: depth);
for (final input in inputs.values) {
input.describe(ctx, indent: indent + 1);
input.accept(visitor, depth: depth + 1);
}
}
}

Set<TradeSymbol> _extractableFrom(SystemWaypoint waypoint) {
if (waypoint.isAsteroid) {
return _minable;
}
if (waypoint.type == WaypointType.GAS_GIANT) {
return _siphonable;
}
return {};
}

WaypointSymbol? _nearestExtractionSiteFor(
SystemsCache systemsCache,
TradeSymbol tradeSymbol,
Expand Down Expand Up @@ -188,29 +152,6 @@ MarketListing? _nearestListingWithExport(
return listings.firstOrNull;
}

// Returns the distance between two waypoints, or null if they are in different
// systems.
int? distanceBetween(
SystemsCache systemsCache,
WaypointSymbol a,
WaypointSymbol b,
) {
final aWaypoint = systemsCache.waypoint(a);
final bWaypoint = systemsCache.waypoint(b);
if (aWaypoint.system != bWaypoint.system) {
return null;
}
return aWaypoint.distanceTo(bWaypoint).toInt();
}

String describeMarket(WaypointSymbol waypointSymbol, MarketPrice? price) {
final name = waypointSymbol.waypointName;
if (price == null) {
return '$name (no market)';
}
return '$name (${price.supply}, ${price.activity})';
}

/// Builds a supply chain.
class SupplyChainBuilder {
/// Create a new supply chain builder.
Expand Down Expand Up @@ -293,9 +234,8 @@ class SupplyChainBuilder {
/// Build a supply chain to source a good for a waypoint.
SupplyLink? buildChainTo(
TradeSymbol tradeSymbol,
WaypointSymbol waypointSymbol, {
int indent = 0,
}) {
WaypointSymbol waypointSymbol,
) {
final listing = _marketListings[waypointSymbol];
// If the end isn't a market this must be a shuttle step.
if (listing == null) {
Expand Down

0 comments on commit 8e1b062

Please sign in to comment.