Files @ r24874:b9bdc5d49a71
Branch filter:

Location: cpp/openttd-patchpack/source/src/order_backup.cpp

Patric Stout
Fix #6319: [Win32] don't use clipping; draw whole screen every frame (#8726)

When we clip the region that is only been redrawn, something
weird happens on Windows. When pushing 60 frames per second on a
60Hz monitor, it appears that the clipped region is often shown
of another frame, instead of the current.

Examples of this are:
- pause the game, move your mouse to the left, and at the right
speed it totally disappears.
- fast aircrafts seem to be in several places at once, weirdly
lagging behind.
- in title screen, moving your mouse gives you the idea it is
jumping places, instead of smooth movements.

In the end, if you do nothing, everything is correct, so it is
eventually consistent. Just when we are firing many BitBlt in
a clipped region, the in-between is not.

What goes wrong exactly, I honestly do not know. On every frame
that we push to the DC is a mouse painted, but visually it
sometimes appears like it is not. Recording with external software
shows it really is there.
It is also not our eyes playing tricks on us, as the first example
makes it really clear the mouse pointer really is not painted.

And to be clear, with the mouse this is easiest reproduceable,
as high-speed objects are influences by this most. But this happens
for all movement that redraws small regions.

Either way, not using clipped regions resolves the issue completely,
and there appears to be little to no penalty (I failed to measure
any impact of drawing the full screen). So better have a good game
than fast code, I guess?
/*
 * This file is part of OpenTTD.
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 */

/** @file order_backup.cpp Handling of order backups. */

#include "stdafx.h"
#include "command_func.h"
#include "core/pool_func.hpp"
#include "network/network.h"
#include "network/network_func.h"
#include "order_backup.h"
#include "vehicle_base.h"
#include "window_func.h"
#include "station_map.h"

#include "safeguards.h"

OrderBackupPool _order_backup_pool("BackupOrder");
INSTANTIATE_POOL_METHODS(OrderBackup)

/** Free everything that is allocated. */
OrderBackup::~OrderBackup()
{
	if (CleaningPool()) return;

	Order *o = this->orders;
	while (o != nullptr) {
		Order *next = o->next;
		delete o;
		o = next;
	}
}

/**
 * Create an order backup for the given vehicle.
 * @param v    The vehicle to make a backup of.
 * @param user The user that is requesting the backup.
 */
OrderBackup::OrderBackup(const Vehicle *v, uint32 user)
{
	this->user             = user;
	this->tile             = v->tile;
	this->group            = v->group_id;

	this->CopyConsistPropertiesFrom(v);

	/* If we have shared orders, store the vehicle we share the order with. */
	if (v->IsOrderListShared()) {
		this->clone = (v->FirstShared() == v) ? v->NextShared() : v->FirstShared();
	} else {
		/* Else copy the orders */
		Order **tail = &this->orders;

		/* Count the number of orders */
		for (const Order *order : v->Orders()) {
			Order *copy = new Order();
			copy->AssignOrder(*order);
			*tail = copy;
			tail = &copy->next;
		}
	}
}

/**
 * Restore the data of this order to the given vehicle.
 * @param v The vehicle to restore to.
 */
void OrderBackup::DoRestore(Vehicle *v)
{
	/* If we had shared orders, recover that */
	if (this->clone != nullptr) {
		DoCommand(0, v->index | CO_SHARE << 30, this->clone->index, DC_EXEC, CMD_CLONE_ORDER);
	} else if (this->orders != nullptr && OrderList::CanAllocateItem()) {
		v->orders.list = new OrderList(this->orders, v);
		this->orders = nullptr;
		/* Make sure buoys/oil rigs are updated in the station list. */
		InvalidateWindowClassesData(WC_STATION_LIST, 0);
	}

	v->CopyConsistPropertiesFrom(this);

	/* Make sure orders are in range */
	v->UpdateRealOrderIndex();
	if (v->cur_implicit_order_index >= v->GetNumOrders()) v->cur_implicit_order_index = v->cur_real_order_index;

	/* Restore vehicle group */
	DoCommand(0, this->group, v->index, DC_EXEC, CMD_ADD_VEHICLE_GROUP);
}

/**
 * Create an order backup for the given vehicle.
 * @param v    The vehicle to make a backup of.
 * @param user The user that is requesting the backup.
 * @note Will automatically remove any previous backups of this user.
 */
/* static */ void OrderBackup::Backup(const Vehicle *v, uint32 user)
{
	/* Don't use reset as that broadcasts over the network to reset the variable,
	 * which is what we are doing at the moment. */
	for (OrderBackup *ob : OrderBackup::Iterate()) {
		if (ob->user == user) delete ob;
	}
	if (OrderBackup::CanAllocateItem()) {
		new OrderBackup(v, user);
	}
}

/**
 * Restore the data of this order to the given vehicle.
 * @param v    The vehicle to restore to.
 * @param user The user that built the vehicle, thus wants to restore.
 * @note After restoration the backup will automatically be removed.
 */
/* static */ void OrderBackup::Restore(Vehicle *v, uint32 user)
{
	for (OrderBackup *ob : OrderBackup::Iterate()) {
		if (v->tile != ob->tile || ob->user != user) continue;

		ob->DoRestore(v);
		delete ob;
	}
}

/**
 * Reset an OrderBackup given a tile and user.
 * @param tile The tile associated with the OrderBackup.
 * @param user The user associated with the OrderBackup.
 * @note Must not be used from the GUI!
 */
/* static */ void OrderBackup::ResetOfUser(TileIndex tile, uint32 user)
{
	for (OrderBackup *ob : OrderBackup::Iterate()) {
		if (ob->user == user && (ob->tile == tile || tile == INVALID_TILE)) delete ob;
	}
}

/**
 * Clear an OrderBackup
 * @param tile  Tile related to the to-be-cleared OrderBackup.
 * @param flags For command.
 * @param p1    Unused.
 * @param p2    User that had the OrderBackup.
 * @param text  Unused.
 * @return The cost of this operation or an error.
 */
CommandCost CmdClearOrderBackup(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
{
	/* No need to check anything. If the tile or user don't exist we just ignore it. */
	if (flags & DC_EXEC) OrderBackup::ResetOfUser(tile == 0 ? INVALID_TILE : tile, p2);

	return CommandCost();
}

/**
 * Reset an user's OrderBackup if needed.
 * @param user The user associated with the OrderBackup.
 * @pre _network_server.
 * @note Must not be used from a command.
 */
/* static */ void OrderBackup::ResetUser(uint32 user)
{
	assert(_network_server);

	for (OrderBackup *ob : OrderBackup::Iterate()) {
		/* If it's not a backup of us, ignore it. */
		if (ob->user != user) continue;

		DoCommandP(0, 0, user, CMD_CLEAR_ORDER_BACKUP);
		return;
	}
}

/**
 * Reset the OrderBackups from GUI/game logic.
 * @param t        The tile of the order backup.
 * @param from_gui Whether the call came from the GUI, i.e. whether
 *                 it must be synced over the network.
 */
/* static */ void OrderBackup::Reset(TileIndex t, bool from_gui)
{
	/* The user has CLIENT_ID_SERVER as default when network play is not active,
	 * but compiled it. A network client has its own variable for the unique
	 * client/user identifier. Finally if networking isn't compiled in the
	 * default is just plain and simple: 0. */
	uint32 user = _networking && !_network_server ? _network_own_client_id : CLIENT_ID_SERVER;

	for (OrderBackup *ob : OrderBackup::Iterate()) {
		/* If it's not a backup of us, ignore it. */
		if (ob->user != user) continue;
		/* If it's not for our chosen tile either, ignore it. */
		if (t != INVALID_TILE && t != ob->tile) continue;

		if (from_gui) {
			/* We need to circumvent the "prevention" from this command being executed
			 * while the game is paused, so use the internal method. Nor do we want
			 * this command to get its cost estimated when shift is pressed. */
			DoCommandPInternal(ob->tile, 0, user, CMD_CLEAR_ORDER_BACKUP, nullptr, nullptr, true, false);
		} else {
			/* The command came from the game logic, i.e. the clearing of a tile.
			 * In that case we have no need to actually sync this, just do it. */
			delete ob;
		}
	}
}

/**
 * Clear the group of all backups having this group ID.
 * @param group The group to clear.
 */
/* static */ void OrderBackup::ClearGroup(GroupID group)
{
	for (OrderBackup *ob : OrderBackup::Iterate()) {
		if (ob->group == group) ob->group = DEFAULT_GROUP;
	}
}

/**
 * Clear/update the (clone) vehicle from an order backup.
 * @param v The vehicle to clear.
 * @pre v != nullptr
 * @note If it is not possible to set another vehicle as clone
 *       "example", then this backed up order will be removed.
 */
/* static */ void OrderBackup::ClearVehicle(const Vehicle *v)
{
	assert(v != nullptr);
	for (OrderBackup *ob : OrderBackup::Iterate()) {
		if (ob->clone == v) {
			/* Get another item in the shared list. */
			ob->clone = (v->FirstShared() == v) ? v->NextShared() : v->FirstShared();
			/* But if that isn't there, remove it. */
			if (ob->clone == nullptr) delete ob;
		}
	}
}

/**
 * Removes an order from all vehicles. Triggers when, say, a station is removed.
 * @param type The type of the order (OT_GOTO_[STATION|DEPOT|WAYPOINT]).
 * @param destination The destination. Can be a StationID, DepotID or WaypointID.
 * @param hangar Only used for airports in the destination.
 *               When false, remove airport and hangar orders.
 *               When true, remove either airport or hangar order.
 */
/* static */ void OrderBackup::RemoveOrder(OrderType type, DestinationID destination, bool hangar)
{
	for (OrderBackup *ob : OrderBackup::Iterate()) {
		for (Order *order = ob->orders; order != nullptr; order = order->next) {
			OrderType ot = order->GetType();
			if (ot == OT_GOTO_DEPOT && (order->GetDepotActionType() & ODATFB_NEAREST_DEPOT) != 0) continue;
			if (ot == OT_GOTO_DEPOT && hangar && !IsHangarTile(ob->tile)) continue; // Not an aircraft? Can't have a hangar order.
			if (ot == OT_IMPLICIT || (IsHangarTile(ob->tile) && ot == OT_GOTO_DEPOT && !hangar)) ot = OT_GOTO_STATION;
			if (ot == type && order->GetDestination() == destination) {
				/* Remove the order backup! If a station/depot gets removed, we can't/shouldn't restore those broken orders. */
				delete ob;
				break;
			}
		}
	}
}