stk-code_catmod/src/items/network_item_manager.cpp

308 lines
11 KiB
C++
Raw Normal View History

//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2018 Joerg Henrichs
//
// This program 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; either version 3
// of the License, or (at your option) any later version.
//
// This program 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 this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#include "items/network_item_manager.hpp"
#include "karts/abstract_kart.hpp"
#include "modes/world.hpp"
#include "network/network_config.hpp"
#include "network/protocols/game_protocol.hpp"
#include "network/rewind_manager.hpp"
#include "network/stk_host.hpp"
//-----------------------------------------------------------------------------
/** Creates one instance of the item manager. */
void NetworkItemManager::create()
{
assert(!m_item_manager);
m_item_manager = new NetworkItemManager();
} // create
// ============================================================================
/** Creates a new instance of the item manager. This is done at startup
* of each race. */
NetworkItemManager::NetworkItemManager()
: Rewinder(/*can be deleted*/false),
ItemManager()
{
m_last_confirmed_item_ticks.lock();
m_last_confirmed_item_ticks.getData().clear();
if (NetworkConfig::get()->isServer())
m_last_confirmed_item_ticks.getData().resize(STKHost::get()->getPeerCount(), 0);
m_last_confirmed_item_ticks.unlock();
} // NetworkItemManager
//-----------------------------------------------------------------------------
/** Destructor. Cleans up all items and meshes stored.
*/
NetworkItemManager::~NetworkItemManager()
{
} // ~NetworkItemManager
//-----------------------------------------------------------------------------
void NetworkItemManager::reset()
{
ItemManager::reset();
} // reset
//-----------------------------------------------------------------------------
/** Copies the initial state at the start of a race as confirmed state.
*/
void NetworkItemManager::saveInitialState()
{
m_confirmed_state_time = 0;
m_confirmed_state.clear();
for(auto i : m_all_items)
{
ItemState *is = new ItemState(*i);
m_confirmed_state.push_back(is);
}
} // saveInitialState
//-----------------------------------------------------------------------------
/** Called when an item is inserted. It makes sure the vector for the
* confirmed state has the right size.
* \param item The item to be added.
* \returns Index of the newly inserted item.
*/
unsigned int NetworkItemManager::insertItem(Item *item)
{
unsigned int index = ItemManager::insertItem(item);
if(index>=m_confirmed_state.size())
{
m_confirmed_state.push_back(NULL);
}
else
{
*m_confirmed_state[index] = *m_all_items[index];
}
return index;
} // insertItem
//-----------------------------------------------------------------------------
/** Called when a kart collects an item. In network games only the server
* acts on this event.
* \param item The item that was collected.
* \param kart The kart that collected the item.
*/
void NetworkItemManager::collectedItem(Item *item, AbstractKart *kart)
{
if(NetworkConfig::get()->isServer())
{
m_item_events.lock();
m_item_events.getData().emplace_back(World::getWorld()->getTimeTicks(),
item->getItemId(),
kart->getWorldKartId());
m_item_events.unlock();
ItemManager::collectedItem(item, kart);
}
else
{
// If we are predicting (i.e. not rewinding), the client
// predicts item collection:
ItemManager::collectedItem(item, kart);
}
} // collectedItem
// ----------------------------------------------------------------------------
/** Called when a new item is created, e.g. bubble gum.
* \param type Type of the item.
* \param parent In case of a dropped item used to avoid that a kart
* is affected by its own items.
*/
Item* NetworkItemManager::dropNewItem(ItemState::ItemType type,
AbstractKart *kart)
{
Item *item = ItemManager::dropNewItem(type, kart);
if(!item) return NULL;
m_item_events.lock();
m_item_events.getData().emplace_back(World::getWorld()->getTimeTicks(), type,
item->getItemId(),
kart->getXYZ() );
m_item_events.unlock();
return item;
} // newItem
// ----------------------------------------------------------------------------
/** Called by the GameProtocol when a confirmation for an item event is
* received by a host. Once all hosts have confirmed an event, it can be
* deleted and won't be send to any clients again.
* \param host_id Host identification of the host confirming the latest
* event time received.
* \param ticks Time at which the last event was received.
*/
void NetworkItemManager::setItemConfirmationTime(int host_id, int ticks)
{
assert(NetworkConfig::get()->isServer());
m_last_confirmed_item_ticks.lock();
if (ticks > m_last_confirmed_item_ticks.getData()[host_id])
m_last_confirmed_item_ticks.getData()[host_id] = ticks;
// Now discard unneeded events, i.e. all events that have
// been confirmed by all clients:
int min_time = 999999;
for (auto i : m_last_confirmed_item_ticks.getData())
if (i < min_time) min_time = i;
m_last_confirmed_item_ticks.unlock();
// Find the last entry before the minimal confirmed time.
// Since the event list is sorted, all events up to this
// entry can be deleted.
m_item_events.lock();
auto p = m_item_events.getData().begin();
while (p != m_item_events.getData().end() && p->getTicks() < min_time)
p++;
m_item_events.getData().erase(m_item_events.getData().begin(), p);
m_item_events.unlock();
// TODO: Get informed when a client drops out!!!
} // setItemConfirmationTime
//-----------------------------------------------------------------------------
/** Saves the state of all items. This is done by using a state that has
* been confirmed by all clients as a base, and then only adding any
* changes applied to that state later. As clients keep on confirming events
* the confirmed event will be moved forward in time, and older events can
* be deleted (and not sent to the clients anymore).
* This function is also called on the client in the first frame of a race
* to save the initial state, which is the first confirmed state by all
* clients.
*/
BareNetworkString* NetworkItemManager::saveState()
{
if(NetworkConfig::get()->isClient())
{
saveInitialState();
return NULL;
}
// On the server:
// ==============
m_item_events.lock();
uint16_t n = (uint16_t)m_item_events.getData().size();
if(n==0)
{
BareNetworkString *s =
new BareNetworkString();
m_item_events.unlock();
return s;
}
BareNetworkString *s =
new BareNetworkString(n * (sizeof(int) + sizeof(uint16_t) +
+ sizeof(uint8_t) ) );
for (auto p : m_item_events.getData())
{
p.saveState(s);
}
m_item_events.unlock();
Log::verbose("NIM", "Including %d item update at %d",
n, World::getWorld()->getTimeTicks());
return s;
} // saveState
//-----------------------------------------------------------------------------
/** Progresses the time for all item by the given number of ticks. Used
* when computing a new state from a confirmed state.
* \param ticks Number of ticks that need to be simulated.
*/
void NetworkItemManager::forwardTime(int ticks)
{
for(auto &i : m_confirmed_state)
{
if (i) i->update(ticks);
} // for m_all_items
} // forwardTime
//-----------------------------------------------------------------------------
/** Restores the state of the items to the current world time. It takes the
* last saved
* using exactly 'count' bytes of the message.
* \param buffer the state content.
* \param count Number of bytes used for this state.
*/
void NetworkItemManager::restoreState(BareNetworkString *buffer, int count)
{
// The state at World::getTimeTicks() needs to be restored. The confirmed
// state in this instance was taken at m_confirmed_state_time. So first
// apply the new events to the confirmed state till we reach the end of
// all new events (which must be <= getTimeTicks() (since the server will
// only send events till the specified state time). Then forward the
// state to getTimeTicks().
int current_time = m_confirmed_state_time;
while(count > 0)
{
// 1) Decode the event in the message
// ----------------------------------
ItemEventInfo iei(buffer, &count);
// 2) If the event needs to be applied, forward
// the time to the time of this event:
// --------------------------------------------
int dt = iei.getTicks() - current_time;
// Skip an event that are 'in the past' (i.e. have been sent again by
// the server because it has not yet received confirmation from all
// clients.
if(dt<0) continue;
// Forward the saved state:
if (dt>0) forwardTime(dt);
// TODO: apply the various events types, atm only collection is supported:
ItemState *item_state = m_confirmed_state[iei.getIndex()];
if(iei.isItemCollection())
{
// An item on the track was collected:
AbstractKart *kart = World::getWorld()->getKart(iei.getKartId());
m_confirmed_state[iei.getIndex()]->collected(kart);
}
current_time = iei.getTicks();
} // while count >0
// Inform the server which events have been received.
GameProtocol::lock()->sendItemEventConfirmation(World::getWorld()->getTimeTicks());
// Forward the confirmed item state till the world time:
int dt = World::getWorld()->getTimeTicks() - current_time;
if(dt>0) forwardTime(dt);
// Restore the state to the current world time:
// ============================================
for(unsigned int i=0; i<m_confirmed_state.size(); i++)
{
Item *item = m_all_items[i];
const ItemState *is = m_confirmed_state[i];
if(is)
*(ItemState*)item = *is;
}
// Now we save the current local
m_confirmed_state_time = World::getWorld()->getTimeTicks();
} // restoreState