Files
src-1.2/engine/server/application/PlanetServer/src/shared/PlanetProxyObject.cpp
T
DarthArgus e65b05ada9 mostly template initializations to prevent undefined behavior in the
(unlikely) chance some of these properties are used
2016-07-24 21:25:03 -07:00

702 lines
23 KiB
C++
Executable File

// ======================================================================
//
// PlanetProxyObject.cpp
// copyright (c) 2001 Sony Online Entertainment
//
// ======================================================================
#include "FirstPlanetServer.h"
#include "PlanetProxyObject.h"
#include "ConfigPlanetServer.h"
#include "GameServerData.h"
#include "PlanetServer.h"
#include "QuadtreeNode.h"
#include "Scene.h"
#include "WatcherConnection.h"
#include "serverNetworkMessages/LoadObjectMessage.h"
#include "serverNetworkMessages/SetAuthoritativeMessage.h"
#include "serverNetworkMessages/UnloadProxyMessage.h"
#include "sharedFoundation/Clock.h"
#include "sharedFoundation/ExitChain.h"
#include "sharedFoundation/MemoryBlockManager.h"
#include "sharedFoundation/Tag.h"
#include "sharedLog/Log.h"
#include "sharedMathArchive/TransformArchive.h"
#include <algorithm>
// ======================================================================
MemoryBlockManager* PlanetProxyObject::memoryBlockManager;
// ======================================================================
PlanetProxyObject::PlanetProxyObject(const NetworkId &objectId) :
m_objectId(objectId),
m_containedBy(NetworkId::cms_invalid),
m_x(0),
m_y(0),
m_z(0),
m_authoritativeServer(0),
m_lastReportedServer(0),
m_interestRadius(0),
m_quadtreeNode(nullptr),
m_authTransferTimeMs(Clock::timeMs()),
m_contents(nullptr),
m_level(0),
m_hibernating(false),
m_templateCrc(0),
m_aiActivity(-1),
m_creationType(-1),
m_objectTypeTag(0)
{
}
// ----------------------------------------------------------------------
PlanetProxyObject::~PlanetProxyObject()
{
if (PlanetServer::getInstance().isWatcherPresent())
{
outputStatusToAll(true);
}
m_quadtreeNode = 0;
if (m_contents)
{
for (std::vector<NetworkId>::const_iterator i = m_contents->begin(); i != m_contents->end(); ++i)
WARNING(true, ("Object %s was deleted without removing contained object %s first", m_objectId.getValueString().c_str(), i->getValueString().c_str()));
delete m_contents;
m_contents = 0;
}
}
// ----------------------------------------------------------------------
/**
* Update an object with new data sent from a game server.
* Checks to see what changed, then takes appropriate actions.
*/
void PlanetProxyObject::update(int x, int y, int z, NetworkId containedBy, uint32 authoritativeServer, int interestRadius, int objectTypeTag, int const level, bool const hibernating, uint32 const templateCrc, int const aiActivity, int const creationType)
{
WARNING_DEBUG_FATAL(m_objectId == NetworkId::cms_invalid, ("Object ID was 0."));
bool watcherUpdateNeeded = false;
m_level = level;
m_hibernating = hibernating;
m_templateCrc = templateCrc;
m_aiActivity = aiActivity;
m_creationType = creationType;
if ((m_authoritativeServer != 0) && (authoritativeServer != m_authoritativeServer) && (containedBy == NetworkId::cms_invalid))
{
static unsigned long authTransferSanityCheckTimeMs = ConfigPlanetServer::getAuthTransferSanityCheckTimeMs();
if (Clock::timeMs() - m_authTransferTimeMs > authTransferSanityCheckTimeMs)
{
DEBUG_WARNING(true, ("Resending auth transfer for %s to %lu due to receiving a stale position update.", m_objectId.getValueString().c_str(), authoritativeServer));
sendAuthorityChange(authoritativeServer, m_authoritativeServer, false);
return;
}
if (ConfigPlanetServer::getLogObjectLoading())
LOG("ObjectLoading", ("Ignoring update for %s from %lu because server was not authoritative.", m_objectId.getValueString().c_str(), authoritativeServer));
return;
}
if (interestRadius > ConfigPlanetServer::getMaxInterestRadius())
interestRadius = ConfigPlanetServer::getMaxInterestRadius();
if (interestRadius > 0 && PlanetServer::getInstance().isInTutorialMode())
interestRadius = 1;
if (containedBy != NetworkId::cms_invalid)
{
// get coordinates from container
PlanetProxyObject *container = Scene::getInstance().findObjectByID(containedBy);
if (container)
{
x = container->getX();
y = container->getY();
z = container->getZ();
}
else
{
LOG("PlanetUpdate", ("Game server said object %s was contained by object %s, but we could not find the container.", m_objectId.getValueString().c_str(), containedBy.getValueString().c_str()));
containedBy = NetworkId::cms_invalid; // so that we'll try again when the object moves again, in case we just got objects out-of-order from the game server
}
}
std::vector<uint32> oldProxyList;
Node *newNode = Scene::getInstance().findNodeByPosition(x, z);
NOT_NULL(newNode);
if (newNode != m_quadtreeNode || m_interestRadius != interestRadius || m_authoritativeServer != authoritativeServer || m_containedBy != containedBy)
{
watcherUpdateNeeded = true;
// For debugging, update the contents of the containers
if (ConfigPlanetServer::getEnableContentsChecking() && (m_containedBy != containedBy))
updateContentsTracking(containedBy);
bool const firstUpdate = (m_quadtreeNode == nullptr);
if (m_quadtreeNode != nullptr) // if this is the first update for this object, m_quadtreeNode will be nullptr because it hasn't been placed anywhere yet
{
removeServerStatistics();
// remember old list of proxies
getServers(oldProxyList);
// remove from the old location
m_quadtreeNode->removeObject(this);
if (ConfigPlanetServer::getLogObjectLoading())
LOG("ObjectLoading", ("unsubscribing nodes for object id=[%s]", m_objectId.getValueString().c_str()));
unsubscribeSurroundingNodes(false);
}
// update data
m_quadtreeNode = newNode;
m_x = x;
m_y = y;
m_z = z;
m_interestRadius = interestRadius;
m_objectTypeTag = objectTypeTag;
m_authoritativeServer = authoritativeServer;
m_containedBy = containedBy;
// make sure the destination is loaded
if (!newNode->isLoaded())
{
if (ConfigPlanetServer::getLogObjectLoading())
LOG("ObjectLoading", ("Loading node %s because object %s entered it.", newNode->getDebugNodeString().c_str(), m_objectId.getValueString().c_str()));
newNode->load();
}
// check if we're on the right server
if (!isAuthorityOk())
{
uint32 preferredServer = m_quadtreeNode->getPreferredServer();
if (m_containedBy == NetworkId::cms_invalid)
{
if (ConfigPlanetServer::getLogObjectLoading())
LOG("ObjectLoading", ("Changing authority for object %s from %lu to %lu because node %s is authoritative on that server.",
m_objectId.getValueString().c_str(), m_authoritativeServer, preferredServer, m_quadtreeNode->getDebugNodeString().c_str()));
changeAuthority(preferredServer, false, false); // changes m_authoritativeServer and sends message
// we received the object for the first time, and we had to
// transfer the object's authority, so we need to make sure
// to unload the object on the current authoritative server,
// if necessary
if (firstUpdate)
oldProxyList.push_back(authoritativeServer);
}
else
{
WARNING(true, ("Programmer bug: Object %s is in container %s, which is node %s. The object should be authoritative on server %i but it is authoritative on server %i.",
m_objectId.getValueString().c_str(), m_containedBy.getValueString().c_str(), m_quadtreeNode->getDebugNodeString().c_str(), m_authoritativeServer, preferredServer));
}
}
// place in the new location
newNode->addObject(this);
if (interestRadius > 0)
{
if (ConfigPlanetServer::getLogObjectLoading())
LOG("ObjectLoading", ("subscribing nodes for object id=[%s]", m_objectId.getValueString().c_str()));
subscribeSurroundingNodes();
}
// check against new interested servers, and send proxy/unproxy as needed
if (m_containedBy == NetworkId::cms_invalid) // we don't manage proxies for containers
{
std::vector<uint32> newProxyList;
getServers(newProxyList);
changeProxies(oldProxyList, newProxyList);
}
addServerStatistics();
}
else
{
if (m_interestRadius > 0 || (abs(m_x - x) > 25) || (abs(m_z - z) > 25))
watcherUpdateNeeded = true;
}
if (PlanetServer::getInstance().isWatcherPresent() && watcherUpdateNeeded)
{
m_x = x;
m_y = y;
m_z = z;
m_objectTypeTag = objectTypeTag;
outputStatusToAll(false);
}
}
// ----------------------------------------------------------------------
void PlanetProxyObject::unsubscribeSurroundingNodes(bool clearZeroSubscriptions)
{
if (m_interestRadius <= 0)
return;
Scene::NodeListType nodelist;
Scene::getInstance().findIntersection(nodelist, m_x, m_z, m_interestRadius);
for (Scene::NodeListType::iterator i = nodelist.begin(); i != nodelist.end(); ++i)
{
(*i)->unsubscribeServer(m_authoritativeServer, 1, clearZeroSubscriptions);
}
}
// ----------------------------------------------------------------------
void PlanetProxyObject::subscribeSurroundingNodes()
{
if (m_interestRadius <= 0)
return;
NOT_NULL(m_quadtreeNode);
Scene::NodeListType nodelist;
Scene::getInstance().findIntersection(nodelist, m_x, m_z, m_interestRadius);
for (Scene::NodeListType::iterator i = nodelist.begin(); i != nodelist.end(); ++i)
{
(*i)->subscribeServer(m_authoritativeServer, 1);
uint32 serverId = (*i)->getPreferredServer();
if (serverId != m_authoritativeServer)
m_quadtreeNode->subscribeServer(serverId, 0); // make sure if we can see them, they can see us
}
}
// ----------------------------------------------------------------------
/**
* Restore the object's authority to the specified game server.
*/
void PlanetProxyObject::restoreAuthority(uint32 gameServer)
{
m_authoritativeServer = gameServer;
m_lastReportedServer = gameServer;
// Send queued messages
PlanetServer::getInstance().sendQueuedMessagesForObject(*this);
}
// ----------------------------------------------------------------------
/**
* Make the object authortiative on a different game server. Sends a
* message to the game servers to change authority and updates
* m_authoritativeServer.
*/
void PlanetProxyObject::changeAuthority(uint32 newGameServer, bool handlingCrash, bool forcedByGame)
{
if (handlingCrash)
sendAuthorityChange(newGameServer, newGameServer, true);
else if (!forcedByGame)
sendAuthorityChange(m_authoritativeServer, newGameServer, false);
m_authoritativeServer = newGameServer;
}
// ----------------------------------------------------------------------
/**
* Make the object authoritative on a different game server and adjust the
* subscription counts.
*/
void PlanetProxyObject::changeAuthorityAndSubscriptions(uint32 newGameServer)
{
unsubscribeSurroundingNodes(false);
changeAuthority(newGameServer, false, false);
subscribeSurroundingNodes();
}
// ----------------------------------------------------------------------
/**
* Called when an object should be unloaded from the world. The object
* should be removed from the active game servers, but not deleted from
* the database.
*
* This function also removes the PlanetProxyObject from all data structures.
* On exit, it is legal to delete the PlanetProxyObject.
*
* @param sendToGameServer Set to true if ForceUnloadObjectMessage should be
* sent to the game server. In some cases there is no need to tell the game
* server to unload the object (such as when handling a crash, for example).
*/
void PlanetProxyObject::unload(bool tellGameServer)
{
if (m_quadtreeNode)
m_quadtreeNode->removeObject(this);
Scene::getInstance().removeObjectFromMap(m_objectId, tellGameServer);
if (tellGameServer)
{
PlanetServer::getInstance().unloadObject(m_objectId, m_authoritativeServer);
}
removeServerStatistics();
if (ConfigPlanetServer::getEnableContentsChecking() && m_containedBy != NetworkId::cms_invalid)
{
PlanetProxyObject *container = Scene::getInstance().findObjectByID(m_containedBy);
if (container)
container->removeContainedObject(m_objectId);
else
WARNING(true, ("Unloading object %s in container %s, but the container could not be found.", m_objectId.getValueString().c_str(), m_containedBy.getValueString().c_str()));
}
}
// ----------------------------------------------------------------------
/**
* Output the object's position to a watcher.
* @param conn the watcher
* @param deleteObject if true, tell the watcher to delete this object.
*/
void PlanetProxyObject::outputStatus(WatcherConnection &conn, bool deleteObject) const
{
conn.addObjectUpdate(m_objectId, m_x, m_z, m_authoritativeServer, m_interestRadius, deleteObject, m_objectTypeTag, m_level, m_hibernating, m_templateCrc, m_aiActivity, m_creationType);
}
// ----------------------------------------------------------------------
void PlanetProxyObject::outputStatusToAll(bool deleteObject) const
{
const PlanetServer::WatcherList &connections = PlanetServer::getInstance().getWatchers();
for (PlanetServer::WatcherList::const_iterator i = connections.begin(); i != connections.end(); ++i)
{
outputStatus(**i, deleteObject);
}
}
// ----------------------------------------------------------------------
/**
* Update the GameServerData with statistics about this object.
*/
void PlanetProxyObject::addServerStatistics() const
{
GameServerData *data = PlanetServer::getInstance().getGameServerData(m_authoritativeServer);
if (data)
{
data->adjustObjectCount(1);
if (m_interestRadius != 0)
{
data->adjustInterestObjectCount(1);
if (isCreature())
data->adjustInterestCreatureObjectCount(1);
}
}
}
// ----------------------------------------------------------------------
/**
* Remove statistics about this object from the GameServerData.
* Should exactly undo addServerStatistics().
*/
void PlanetProxyObject::removeServerStatistics() const
{
GameServerData *data = PlanetServer::getInstance().getGameServerData(m_authoritativeServer);
if (data)
{
data->adjustObjectCount(-1);
if (m_interestRadius != 0)
{
data->adjustInterestObjectCount(-1);
if (isCreature())
data->adjustInterestCreatureObjectCount(-1);
}
}
}
// ----------------------------------------------------------------------
/**
* Set up memory pool
*/
void PlanetProxyObject::install()
{
memoryBlockManager = new MemoryBlockManager("PlanetProxyObject::memoryBlockManager", true, sizeof(PlanetProxyObject), 0, 0, 0);
ExitChain::add(&remove, "PlanetProxyObject::remove");
}
// ----------------------------------------------------------------------
void PlanetProxyObject::remove()
{
WARNING_DEBUG_FATAL(!memoryBlockManager, ("PlanetProxyObject is not installed"));
delete memoryBlockManager;
memoryBlockManager = 0;
}
// ----------------------------------------------------------------------
void * PlanetProxyObject::operator new(size_t size)
{
UNREF(size);
WARNING_DEBUG_FATAL(!memoryBlockManager, ("PlanetProxyObject is not installed"));
// do not try to alloc a descendant class with this allocator
WARNING_DEBUG_FATAL(size != sizeof(PlanetProxyObject), ("bad size"));
return memoryBlockManager->allocate();
}
// ----------------------------------------------------------------------
void PlanetProxyObject::operator delete(void* pointer)
{
WARNING_DEBUG_FATAL(!memoryBlockManager, ("PlanetProxyObject is not installed"));
memoryBlockManager->free(pointer);
}
// ----------------------------------------------------------------------
void PlanetProxyObject::sendAuthorityChange(uint32 currentAuthServer, uint32 newAuthServer, bool handlingCrash)
{
DEBUG_WARNING(!isAuthorityClean(), ("Object %s attempted to change authority before previous authority change was complete.", getObjectId().getValueString().c_str()));
SetAuthoritativeMessage const auth(getObjectId(), newAuthServer, false, handlingCrash, NetworkId::cms_invalid, Transform::identity, false);
PlanetServer::getInstance().sendToGameServer(currentAuthServer, auth);
m_authTransferTimeMs = Clock::timeMs();
}
// ----------------------------------------------------------------------
void PlanetProxyObject::sendAddProxy(uint32 proxyServer)
{
if (ConfigPlanetServer::getLogObjectLoading())
LOG("ObjectLoading", ("Gameserver %lu needs a proxy of object %s", proxyServer, m_objectId.getValueString().c_str()));
if (isAuthorityClean())
{
LoadObjectMessage const loadMsg(getObjectId(), proxyServer);
PlanetServer::getInstance().sendToGameServer(m_authoritativeServer, loadMsg);
}
else
{
LoadObjectMessage * const loadMsg = new LoadObjectMessage(getObjectId(), proxyServer);
PlanetServer::getInstance().queueMessageForObject(getObjectId(), loadMsg);
}
}
// ----------------------------------------------------------------------
void PlanetProxyObject::sendRemoveProxy(uint32 proxyServer)
{
if (ConfigPlanetServer::getLogObjectLoading())
LOG("ObjectLoading", ("Gameserver %lu no longer needs a proxy of object %s", proxyServer, m_objectId.getValueString().c_str()));
if (isAuthorityClean())
{
UnloadProxyMessage msg(getObjectId(), proxyServer);
PlanetServer::getInstance().sendToGameServer(m_authoritativeServer, msg);
}
else
{
UnloadProxyMessage *msg = new UnloadProxyMessage(getObjectId(), proxyServer);
PlanetServer::getInstance().queueMessageForObject(getObjectId(), msg);
}
}
// ----------------------------------------------------------------------
/**
* Called when we receieve a message about the object from a game server.
* Used to keep track of which server is sending us updates, and to send
* any messages that were waiting for an authority change.
*/
void PlanetProxyObject::onReceivedMessageFromServer(uint32 server)
{
if (m_lastReportedServer != m_authoritativeServer && server == m_authoritativeServer)
// we've received a message from the new authoritative server. Send queued messages:
PlanetServer::getInstance().sendQueuedMessagesForObject(*this);
m_lastReportedServer = server;
}
// ----------------------------------------------------------------------
/**
* Get the list of servers that should have proxies of this object.
*/
void PlanetProxyObject::getServers(std::vector<uint32> &serverList) const
{
Node *effectiveNode;
if (m_containedBy == NetworkId::cms_invalid)
{
effectiveNode = m_quadtreeNode;
}
else
{
PlanetProxyObject *container = Scene::getInstance().findObjectByID(m_containedBy);
WARNING_STRICT_FATAL(!container, ("Object %s is in container %s, which couldn't be found", m_objectId.getValueString().c_str(), m_containedBy.getValueString().c_str()));
if (container)
effectiveNode = container->getNode();
else
effectiveNode = m_quadtreeNode;
}
if (effectiveNode)
effectiveNode->getServers(serverList);
}
// ----------------------------------------------------------------------
/**
* Given a list of servers that have a proxy and a list of servers
* that should have a proxy, send messages to update the servers.
* Note: O(n^2), assumes both lists are small, otherwise it would be better
* to sort them first.
*/
void PlanetProxyObject::changeProxies(const std::vector<uint32> &oldServers, const std::vector<uint32> &newServers)
{
{
for (std::vector<uint32>::const_iterator i = oldServers.begin(); i != oldServers.end(); ++i)
{
if (*i != m_authoritativeServer)
{
bool removeNeeded = true;
for (std::vector<uint32>::const_iterator j = newServers.begin(); j != newServers.end(); ++j)
{
if (*i == *j)
{
removeNeeded = false;
break;
}
}
if (removeNeeded)
sendRemoveProxy(*i);
}
}
}
{
for (std::vector<uint32>::const_iterator i = newServers.begin(); i != newServers.end(); ++i)
{
if (*i != m_authoritativeServer)
{
bool addNeeded = true;
for (std::vector<uint32>::const_iterator j = oldServers.begin(); j != oldServers.end(); ++j)
{
if (*i == *j)
{
addNeeded = false;
break;
}
}
if (addNeeded)
sendAddProxy(*i);
}
}
}
}
// ----------------------------------------------------------------------
/**
* Check whether we are on a server that's acceptable for where we are.
*/
bool PlanetProxyObject::isAuthorityOk() const
{
if (m_authoritativeServer == m_quadtreeNode->getPreferredServer())
return true;
if (m_quadtreeNode->isBorderNode() && m_quadtreeNode->isServerSubscribed(m_authoritativeServer))
return true;
else
return false;
}
// ----------------------------------------------------------------------
/**
* Check for whether a desired server would be OK for where we are.
* The server is OK if it is the preferred server. If the object is
* a creature, the server is also OK if the node is a border node and
* the server has a subscription to the node. Non-creatures must always
* be on the preferred server.
*/
bool PlanetProxyObject::wouldAuthorityBeOk(uint32 possibleServer) const
{
if (possibleServer == m_quadtreeNode->getPreferredServer())
return true;
if (isCreature() && m_quadtreeNode->isBorderNode() && m_quadtreeNode->isServerSubscribed(possibleServer))
return true;
else
return false;
}
// ----------------------------------------------------------------------
void PlanetProxyObject::addContainedObject(const NetworkId &theObject)
{
if (!m_contents)
{
m_contents = new std::vector<NetworkId>;
}
for (std::vector<NetworkId>::const_iterator i = m_contents->begin(); i != m_contents->end(); ++i)
{
if (*i == theObject)
WARNING(true, ("Object %s was placed in container %s twice.", theObject.getValueString().c_str(), m_objectId.getValueString().c_str()));
}
m_contents->push_back(theObject);
}
// ----------------------------------------------------------------------
void PlanetProxyObject::removeContainedObject(const NetworkId &theObject)
{
if (!m_contents)
{
WARNING(true, ("Attempted to remove object %s from object %s, but that object has no contents.", theObject.getValueString().c_str(), m_objectId.getValueString().c_str()));
return;
}
std::vector<NetworkId>::iterator newEnd = std::remove(m_contents->begin(), m_contents->end(), theObject);
if (newEnd == m_contents->end())
{
WARNING(true, ("Attempted to remove object %s from object %s, but it was not in the container.", theObject.getValueString().c_str(), m_objectId.getValueString().c_str()));
return;
}
m_contents->erase(newEnd, m_contents->end());
}
// ----------------------------------------------------------------------
/**
* Helper function to update the contents tracking lists, in order to
* debug various authority problems
*/
void PlanetProxyObject::updateContentsTracking(const NetworkId &newContainedBy)
{
if (m_containedBy != NetworkId::cms_invalid)
{
PlanetProxyObject *container = Scene::getInstance().findObjectByID(m_containedBy);
if (container)
container->removeContainedObject(m_objectId);
else
WARNING(true, ("Removing object %s from container %s, but the container could not be found.", m_objectId.getValueString().c_str(), m_containedBy.getValueString().c_str()));
}
if (newContainedBy != NetworkId::cms_invalid)
{
PlanetProxyObject *container = Scene::getInstance().findObjectByID(newContainedBy);
if (container)
container->addContainedObject(m_objectId);
else
WARNING(true, ("Adding object %s to container %s, but the container could not be found.", m_objectId.getValueString().c_str(), newContainedBy.getValueString().c_str()));
}
}
// ----------------------------------------------------------------------
bool PlanetProxyObject::isCreature() const
{
return m_objectTypeTag == TAG(C, R, E, O);
}
// ======================================================================