Add road tile cached one way state, one way road section detection

This commit is contained in:
Jonathan G Rennison
2020-10-26 22:17:25 +00:00
parent 2fe5d4339b
commit ffe3c769a3
16 changed files with 441 additions and 104 deletions

View File

@@ -38,6 +38,7 @@
#include "company_gui.h"
#include "road_func.h"
#include "roadstop_base.h"
#include "scope.h"
#include "table/strings.h"
#include "table/roadtypes.h"
@@ -187,6 +188,229 @@ bool RoadVehiclesAreBuilt()
return !RoadVehicle::Iterate().empty();
}
static DisallowedRoadDirections GetOneWayRoadTileDisallowedRoadDirections(TileIndex tile)
{
if (IsNormalRoadTile(tile)) return GetDisallowedRoadDirections(tile);
if (IsDriveThroughStopTile(tile)) return GetDriveThroughStopDisallowedRoadDirections(tile);
return DRD_NONE;
}
static DiagDirection OneWaySideJunctionRoadRoadBitsToDiagDir(RoadBits bits)
{
/*
* Drive on left missing bit:
* ROAD_SE (bit 2) -> DIAGDIR_NE (0)
* ROAD_SW (bit 1) -> DIAGDIR_SE (1)
* ROAD_NW (bit 0) -> DIAGDIR_SW (2)
* ROAD_NE (bit 3) -> DIAGDIR_NW (3)
*/
uint8 bit = FIND_FIRST_BIT(bits ^ ROAD_ALL);
bit ^= 3;
return (DiagDirection)((bit + 3 + (_settings_game.vehicle.road_side * 2)) % 4);
}
inline bool IsOneWaySideJunctionRoadDRDsPresent(TileIndex tile, DiagDirection dir)
{
const DisallowedRoadDirections diagdir_to_drd[DIAGDIR_END] = { DRD_NORTHBOUND, DRD_NORTHBOUND, DRD_SOUTHBOUND, DRD_SOUTHBOUND };
TileIndexDiffC ti = TileIndexDiffCByDiagDir(dir);
TileIndex ahead = AddTileIndexDiffCWrap(tile, ti);
if (ahead == INVALID_TILE || GetOneWayRoadTileDisallowedRoadDirections(ahead) != diagdir_to_drd[dir]) return false;
TileIndex behind = AddTileIndexDiffCWrap(tile, { (int16)(-ti.x), (int16)(-ti.y) });
if (behind == INVALID_TILE || GetOneWayRoadTileDisallowedRoadDirections(behind) != diagdir_to_drd[dir]) return false;
return true;
}
static btree::btree_set<TileIndex> _road_cache_one_way_state_pending_tiles;
static btree::btree_set<TileIndex> _road_cache_one_way_state_pending_interpolate_tiles;
static bool _defer_update_road_cache_one_way_state = false;
bool _mark_tile_dirty_on_road_cache_one_way_state_update = false;
static void UpdateTileRoadCachedOneWayState(TileIndex tile)
{
if (unlikely(_mark_tile_dirty_on_road_cache_one_way_state_update)) MarkTileGroundDirtyByTile(tile, VMDF_NOT_MAP_MODE);
DisallowedRoadDirections drd = GetOneWayRoadTileDisallowedRoadDirections(tile);
if (drd != DRD_NONE) {
SetRoadCachedOneWayState(tile, (RoadCachedOneWayState)drd);
return;
}
if (IsNormalRoadTile(tile)) {
RoadBits bits = GetRoadBits(tile, RTT_ROAD);
if (HasExactlyOneBit(bits ^ ROAD_ALL)) {
DiagDirection dir = OneWaySideJunctionRoadRoadBitsToDiagDir(bits);
if (IsOneWaySideJunctionRoadDRDsPresent(tile, dir)) {
DiagDirection side_dir = (DiagDirection)((dir + 3 + (_settings_game.vehicle.road_side * 2)) % 4);
TileIndexDiffC ti = TileIndexDiffCByDiagDir(side_dir);
TileIndex side = AddTileIndexDiffCWrap(tile, ti);
const DisallowedRoadDirections diagdir_to_drd[DIAGDIR_END] = { DRD_SOUTHBOUND, DRD_SOUTHBOUND, DRD_NORTHBOUND, DRD_NORTHBOUND };
SetRoadCachedOneWayState(tile, (GetOneWayRoadTileDisallowedRoadDirections(side) & diagdir_to_drd[side_dir]) ? RCOWS_SIDE_JUNCTION_NO_EXIT : RCOWS_SIDE_JUNCTION);
return;
}
}
}
if (!IsTileType(tile, MP_STATION)) _road_cache_one_way_state_pending_interpolate_tiles.insert(tile);
SetRoadCachedOneWayState(tile, RCOWS_NORMAL);
}
/* Do not re-order, see: RoadCachedOneWayState */
enum InterpolateRoadResult {
IRR_NONE,
IRR_OUT,
IRR_IN
};
static TileIndex InterpolateRoadFollowTileStep(TileIndex tile, uint8 bit)
{
DiagDirection outgoing = (DiagDirection)(bit ^ 3);
if (IsTileType(tile, MP_TUNNELBRIDGE) && GetTunnelBridgeDirection(tile) == outgoing) {
return GetOtherTunnelBridgeEnd(tile);
}
TileIndexDiffC ti = TileIndexDiffCByDiagDir(outgoing);
TileIndex next = AddTileIndexDiffCWrap(tile, ti);
if (next == INVALID_TILE) return INVALID_TILE;
if (IsTileType(next, MP_TUNNELBRIDGE) && GetTunnelBridgeDirection(next) == ReverseDiagDir(outgoing)) {
return INVALID_TILE;
}
return next;
}
static InterpolateRoadResult InterpolateRoadFollowRoadBit(TileIndex tile, uint8 bit)
{
const TileIndex start = tile;
do {
TileIndex next = InterpolateRoadFollowTileStep(tile, bit);
if (next == INVALID_TILE) return IRR_NONE;
DisallowedRoadDirections drd = GetOneWayRoadTileDisallowedRoadDirections(next);
if (drd == DRD_BOTH) return IRR_NONE;
if (drd != DRD_NONE) {
const DisallowedRoadDirections outgoing_drd_by_exit_bit[4] = { DRD_SOUTHBOUND, DRD_SOUTHBOUND, DRD_NORTHBOUND, DRD_NORTHBOUND };
return outgoing_drd_by_exit_bit[bit] == drd ? IRR_OUT : IRR_IN;
}
if (IsTileType(next, MP_STATION)) return IRR_NONE;
RoadBits incoming = (RoadBits)(1 << (bit ^ 2));
RoadBits rb = GetAnyRoadBits(next, RTT_ROAD, true);
if ((incoming & rb) == 0) return IRR_NONE;
RoadBits remaining = rb & ~incoming;
if (!HasExactlyOneBit(remaining)) return IRR_NONE;
tile = next;
bit = FIND_FIRST_BIT(remaining);
} while (tile != start);
return IRR_NONE;
}
static void InterpolateRoadFollowRoadBitSetState(TileIndex tile, uint8 bit, InterpolateRoadResult irr)
{
const TileIndex start = tile;
do {
if (irr == IRR_NONE) {
SetRoadCachedOneWayState(tile, RCOWS_NORMAL);
} else {
uint8 inbit = FIND_FIRST_BIT(GetAnyRoadBits(tile, RTT_ROAD, true) & ~(1 << bit));
/* inbit bit piece Outgoing Trackdir IRR_IN case
*
* 0 1 ROAD_W TRACKDIR_LEFT_S RCOWS_NON_JUNCTION_A
* 0 2 ROAD_Y TRACKDIR_Y_SE RCOWS_NON_JUNCTION_A
* 0 3 ROAD_N TRACKDIR_UPPER_E RCOWS_NON_JUNCTION_A
*
* 1 0 ROAD_W TRACKDIR_LEFT_N RCOWS_NON_JUNCTION_B
* 1 2 ROAD_S TRACKDIR_LOWER_E RCOWS_NON_JUNCTION_A
* 1 3 ROAD_X TRACKDIR_X_NE RCOWS_NON_JUNCTION_A
*
* 2 0 ROAD_Y TRACKDIR_Y_NW RCOWS_NON_JUNCTION_B
* 2 1 ROAD_S TRACKDIR_LOWER_W RCOWS_NON_JUNCTION_B
* 2 3 ROAD_E TRACKDIR_RIGHT_N RCOWS_NON_JUNCTION_B
*
* 3 0 ROAD_N TRACKDIR_UPPER_W RCOWS_NON_JUNCTION_B
* 3 1 ROAD_X TRACKDIR_X_SW RCOWS_NON_JUNCTION_B
* 3 2 ROAD_E TRACKDIR_RIGHT_S RCOWS_NON_JUNCTION_A
*/
const uint16 bits_to_rcows = 0x3B10;
SetRoadCachedOneWayState(tile, (RoadCachedOneWayState)(irr ^ (HasBit(bits_to_rcows, (inbit << 2) | bit) ? 0 : 3)));
}
_road_cache_one_way_state_pending_interpolate_tiles.erase(tile);
if (unlikely(_mark_tile_dirty_on_road_cache_one_way_state_update)) MarkTileGroundDirtyByTile(tile, VMDF_NOT_MAP_MODE);
TileIndex next = InterpolateRoadFollowTileStep(tile, bit);
if (next == INVALID_TILE) return;
DisallowedRoadDirections drd = GetOneWayRoadTileDisallowedRoadDirections(next);
if (drd != DRD_NONE) {
return;
}
if (IsTileType(next, MP_STATION)) return;
RoadBits incoming = (RoadBits)(1 << (bit ^ 2));
RoadBits rb = GetAnyRoadBits(next, RTT_ROAD, true);
if ((incoming & rb) == 0) return;
RoadBits remaining = rb & ~incoming;
if (!HasExactlyOneBit(remaining)) return;
tile = next;
bit = FIND_FIRST_BIT(remaining);
} while (tile != start);
}
static void InterpolateRoadCachedOneWayStates()
{
while (!_road_cache_one_way_state_pending_interpolate_tiles.empty()) {
auto iter = _road_cache_one_way_state_pending_interpolate_tiles.begin();
TileIndex tile = *iter;
_road_cache_one_way_state_pending_interpolate_tiles.erase(iter);
const RoadBits bits = GetAnyRoadBits(tile, RTT_ROAD, true);
if (CountBits(bits) != 2) continue;
uint8 first_bit = FIND_FIRST_BIT(bits);
uint8 second_bit = FIND_FIRST_BIT(KillFirstBit(bits));
InterpolateRoadResult first_irr = InterpolateRoadFollowRoadBit(tile, first_bit);
InterpolateRoadResult second_irr = first_irr;
if (first_irr != IRR_NONE) {
second_irr = InterpolateRoadFollowRoadBit(tile, second_bit);
if (second_irr == IRR_NONE || second_irr == first_irr) first_irr = second_irr = IRR_NONE;
}
InterpolateRoadFollowRoadBitSetState(tile, first_bit, first_irr);
InterpolateRoadFollowRoadBitSetState(tile, second_bit, second_irr);
}
}
void RecalculateRoadCachedOneWayStates()
{
for (TileIndex tile = 0; tile != MapSize(); tile++) {
if (MayHaveRoad(tile)) UpdateTileRoadCachedOneWayState(tile);
}
InterpolateRoadCachedOneWayStates();
}
void UpdateRoadCachedOneWayStatesAroundTile(TileIndex tile)
{
if (_generating_world) return;
auto check_tile = [](TileIndex t) {
if (_defer_update_road_cache_one_way_state) {
_road_cache_one_way_state_pending_tiles.insert(t);
} else if (MayHaveRoad(t)) {
UpdateTileRoadCachedOneWayState(t);
}
};
check_tile(tile);
uint x_offset = TileXY(1, 0);
if (tile >= x_offset) check_tile(tile - x_offset);
if (tile + x_offset < MapSize()) check_tile(tile + x_offset);
uint y_offset = TileXY(0, 1);
if (tile >= y_offset) check_tile(tile - y_offset);
if (tile + y_offset < MapSize()) check_tile(tile + y_offset);
if (!_defer_update_road_cache_one_way_state) InterpolateRoadCachedOneWayStates();
}
void FlushDeferredUpdateRoadCachedOneWayStates()
{
_defer_update_road_cache_one_way_state = false;
for (TileIndex t : _road_cache_one_way_state_pending_tiles) {
if (MayHaveRoad(t)) UpdateTileRoadCachedOneWayState(t);
}
_road_cache_one_way_state_pending_tiles.clear();
InterpolateRoadCachedOneWayStates();
}
/**
* Update road infrastructure counts for a company.
* @param rt Road type to update count of.
@@ -470,6 +694,10 @@ static CommandCost RemoveRoad(TileIndex tile, DoCommandFlag flags, RoadBits piec
/* Todo: Change this to be more fine-grained if necessary */
NotifyRoadLayoutChanged(false);
if (rtt == RTT_ROAD) {
UpdateRoadCachedOneWayStatesAroundTile(tile);
UpdateRoadCachedOneWayStatesAroundTile(other_end);
}
}
} else {
assert_tile(IsDriveThroughStopTile(tile), tile);
@@ -480,6 +708,9 @@ static CommandCost RemoveRoad(TileIndex tile, DoCommandFlag flags, RoadBits piec
SetRoadType(tile, rtt, INVALID_ROADTYPE);
MarkTileDirtyByTile(tile);
NotifyRoadLayoutChanged(false);
if (rtt == RTT_ROAD) {
UpdateRoadCachedOneWayStatesAroundTile(tile);
}
}
}
return cost;
@@ -558,6 +789,9 @@ static CommandCost RemoveRoad(TileIndex tile, DoCommandFlag flags, RoadBits piec
SetRoadBits(tile, present, rtt);
MarkTileDirtyByTile(tile);
}
if (rtt == RTT_ROAD) {
UpdateRoadCachedOneWayStatesAroundTile(tile);
}
}
CommandCost cost(EXPENSES_CONSTRUCTION, CountBits(pieces) * RoadClearCost(existing_rt));
@@ -596,6 +830,9 @@ static CommandCost RemoveRoad(TileIndex tile, DoCommandFlag flags, RoadBits piec
}
MarkTileDirtyByTile(tile);
YapfNotifyTrackLayoutChange(tile, railtrack);
if (rtt == RTT_ROAD) {
UpdateRoadCachedOneWayStatesAroundTile(tile);
}
}
return CommandCost(EXPENSES_CONSTRUCTION, RoadClearCost(existing_rt) * 2);
}
@@ -771,6 +1008,7 @@ CommandCost CmdBuildRoad(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32
SetDisallowedRoadDirections(tile, dis_new);
MarkTileDirtyByTile(tile);
NotifyRoadLayoutChanged(CountBits(dis_existing) > CountBits(dis_new));
UpdateRoadCachedOneWayStatesAroundTile(tile);
}
return CommandCost();
}
@@ -866,6 +1104,9 @@ CommandCost CmdBuildRoad(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32
SetCrossingReservation(tile, reserved);
UpdateLevelCrossing(tile, false);
if (RoadLayoutChangeNotificationEnabled(true)) NotifyRoadLayoutChangedIfTileNonLeaf(tile, rtt, GetCrossingRoadBits(tile));
if (rtt == RTT_ROAD) {
UpdateRoadCachedOneWayStatesAroundTile(tile);
}
MarkTileDirtyByTile(tile);
}
return CommandCost(EXPENSES_CONSTRUCTION, 2 * RoadBuildCost(rt));
@@ -897,6 +1138,7 @@ CommandCost CmdBuildRoad(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32
rs->ChangeDriveThroughDisallowedRoadDirections(dis_new);
MarkTileDirtyByTile(tile);
NotifyRoadLayoutChanged(CountBits(dis_existing) > CountBits(dis_new));
UpdateRoadCachedOneWayStatesAroundTile(tile);
}
return CommandCost();
}
@@ -1028,6 +1270,10 @@ CommandCost CmdBuildRoad(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32
AddRoadTunnelBridgeInfrastructure(tile, other_end);
NotifyRoadLayoutChanged(true);
if (rtt == RTT_ROAD) {
UpdateRoadCachedOneWayStatesAroundTile(tile);
UpdateRoadCachedOneWayStatesAroundTile(other_end);
}
DirtyAllCompanyInfrastructureWindows();
}
@@ -1144,6 +1390,9 @@ do_clear:;
MarkTileDirtyByTile(other_end);
MarkTileDirtyByTile(tile);
}
if (rtt == RTT_ROAD) {
UpdateRoadCachedOneWayStatesAroundTile(other_end);
}
NotifyRoadLayoutChanged(true);
break;
}
@@ -1171,6 +1420,9 @@ do_clear:;
SetDisallowedRoadDirections(tile, IsStraightRoad(existing) ?
GetDisallowedRoadDirections(tile) ^ toggle_drd : DRD_NONE);
}
if (rtt == RTT_ROAD) {
UpdateRoadCachedOneWayStatesAroundTile(tile);
}
MarkTileDirtyByTile(tile);
}
@@ -1252,6 +1504,11 @@ CommandCost CmdBuildLongRoad(TileIndex start_tile, DoCommandFlag flags, uint32 p
bool had_success = false;
bool is_ai = HasBit(p2, 11);
_defer_update_road_cache_one_way_state = true;
auto guard = scope_guard([]() {
FlushDeferredUpdateRoadCachedOneWayStates();
});
/* Start tile is the first tile clicked by the user. */
for (;;) {
RoadBits bits = AxisToRoadBits(axis);
@@ -1335,6 +1592,11 @@ CommandCost CmdRemoveLongRoad(TileIndex start_tile, DoCommandFlag flags, uint32
p2 ^= IsInsideMM(p2 & 3, 1, 3) ? 3 : 0;
}
_defer_update_road_cache_one_way_state = true;
auto guard = scope_guard([]() {
FlushDeferredUpdateRoadCachedOneWayStates();
});
Money money = GetAvailableMoneyForCommand();
TileIndex tile = start_tile;
CommandCost last_error = CMD_ERROR;
@@ -2258,16 +2520,40 @@ static TrackStatus GetTileTrackStatus_Road(TileIndex tile, TransportType mode, u
switch (GetRoadTileType(tile)) {
case ROAD_TILE_NORMAL: {
const uint drd_to_multiplier[DRD_END] = { 0x101, 0x100, 0x1, 0x0 };
const TrackdirBits left_turns = TRACKDIR_BIT_LOWER_W | TRACKDIR_BIT_LEFT_N | TRACKDIR_BIT_UPPER_E | TRACKDIR_BIT_RIGHT_S;
const TrackdirBits right_turns = TRACKDIR_BIT_LOWER_E | TRACKDIR_BIT_LEFT_S | TRACKDIR_BIT_UPPER_W | TRACKDIR_BIT_RIGHT_N;
const TrackdirBits no_exit_turns[4] = {
TRACKDIR_BIT_RIGHT_S | TRACKDIR_BIT_LOWER_E, // ROAD_NW
TRACKDIR_BIT_RIGHT_N | TRACKDIR_BIT_UPPER_E, // ROAD_SW
TRACKDIR_BIT_LEFT_N | TRACKDIR_BIT_UPPER_W, // ROAD_SE
TRACKDIR_BIT_LEFT_S | TRACKDIR_BIT_LOWER_W // ROAD_NE
};
RoadBits bits = GetRoadBits(tile, rtt);
/* no roadbit at this side of tile, return 0 */
if (side != INVALID_DIAGDIR && (DiagDirToRoadBits(side) & bits) == 0) break;
uint multiplier = drd_to_multiplier[(rtt == RTT_TRAM) ? DRD_NONE : GetDisallowedRoadDirections(tile)];
if (!HasRoadWorks(tile)) trackdirbits = (TrackdirBits)(_road_trackbits[bits] * multiplier);
if (!HasRoadWorks(tile)) {
RoadCachedOneWayState rcows = (rtt == RTT_TRAM) ? RCOWS_NORMAL : GetRoadCachedOneWayState(tile);
switch (rcows) {
case RCOWS_NORMAL:
case RCOWS_NON_JUNCTION_A:
case RCOWS_NON_JUNCTION_B:
case RCOWS_NO_ACCESS:
trackdirbits = (TrackdirBits)(_road_trackbits[bits] * drd_to_multiplier[(DisallowedRoadDirections)rcows]);
break;
extern TrackdirBits MaskOneWaySideJunctionRoad(TileIndex tile, RoadBits bits);
if (rtt == RTT_ROAD && HasExactlyOneBit(bits ^ ROAD_ALL)) trackdirbits &= MaskOneWaySideJunctionRoad(tile, bits);
case RCOWS_SIDE_JUNCTION:
case RCOWS_SIDE_JUNCTION_NO_EXIT:
trackdirbits = (TrackdirBits)((_road_trackbits[bits] * 0x101) & ~(_settings_game.vehicle.road_side ? left_turns : right_turns));
if (rcows == RCOWS_SIDE_JUNCTION_NO_EXIT) trackdirbits &= ~no_exit_turns[FIND_FIRST_BIT(bits ^ ROAD_ALL) & 3];
break;
default:
NOT_REACHED();
}
}
break;
}