// // 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; igetTimeTicks(); } // restoreState