/** Rewinds to the specified time, then goes forward till the current * World::getTime() is reached again: it will replay everything before * World::getTime(), but not the events at World::getTime() (or later)/ * \param rewind_ticks Time to rewind to. * \param now_ticks Up to which ticks events are replayed: up to but * EXCLUDING new_ticks (the event at now_ticks are played in * the calling subroutine playEventsTill). * \param fast_forward If true, then only rewinders in network will be * updated, but not the physics. */ void RewindManager::rewindTo(int rewind_ticks, int now_ticks, bool fast_forward) { assert(!m_is_rewinding); bool is_history = history->replayHistory(); history->setReplayHistory(false); // First save all current transforms so that the error // can be computed between the transforms before and after // the rewind. for (auto& p : m_all_rewinder) { if (auto r = p.second.lock()) r->saveTransform(); } // Then undo the rewind infos going backwards in time // -------------------------------------------------- m_is_rewinding = true; // This will go back till the first confirmed state is found before // the specified rewind ticks. int exact_rewind_ticks = m_rewind_queue.undoUntil(rewind_ticks); // Rewind the required state(s) // ---------------------------- World *world = World::getWorld(); // Now start the rewind with the full state. It is important that the // world time is set first, since e.g. the NetworkItem manager relies // on having the access to the 'confirmed' state time using // the world timer. world->setTicksForRewind(exact_rewind_ticks); // Get the (first) full state to which we have to rewind RewindInfo *current = m_rewind_queue.getCurrent(); assert(current->isState()); // Restore states from the exact rewind time // ----------------------------------------- auto it = m_local_state.find(exact_rewind_ticks); if (it != m_local_state.end()) { for (auto& restore_local_state : it->second) { if (restore_local_state) restore_local_state(); } for (auto it = m_local_state.begin(); it != m_local_state.end();) { if (it->first <= exact_rewind_ticks) it = m_local_state.erase(it); else break; } } else if (!fast_forward) { Log::warn("RewindManager", "Missing local state at ticks %d", exact_rewind_ticks); } // A loop in case that we should split states into several smaller ones: while (current && current->getTicks() == exact_rewind_ticks && current->isState() ) { current->restore(); m_rewind_queue.next(); current = m_rewind_queue.getCurrent(); } // Now go forward through the list of rewind infos till we reach 'now': while (world->getTicksSinceStart() < now_ticks) { m_rewind_queue.replayAllEvents(world->getTicksSinceStart()); // Now simulate the next time step if (!fast_forward) world->updateWorld(1); #undef SHOW_ROLLBACK #ifdef SHOW_ROLLBACK irr_driver->update(stk_config->ticks2Time(1)); #endif world->updateTime(1); } // while (world->getTicks() < current_ticks) // Now compute the errors which need to be visually smoothed for (auto& p : m_all_rewinder) { if (auto r = p.second.lock()) r->computeError(); } history->setReplayHistory(is_history); m_is_rewinding = false; mergeRewindInfoEventFunction(); } // rewindTo
/** Rewinds to the specified time, then goes forward till the current * World::getTime() is reached again: it will replay everything before * World::getTime(), but not the events at World::getTime() (or later)/ * \param rewind_ticks Time to rewind to. * \param now_ticks Up to which ticks events are replayed: up to but * EXCLUDING new_ticks (the event at now_ticks are played in * the calling subroutine playEventsTill). */ void RewindManager::rewindTo(int rewind_ticks, int now_ticks) { assert(!m_is_rewinding); bool is_history = history->replayHistory(); history->setReplayHistory(false); // First save all current transforms so that the error // can be computed between the transforms before and after // the rewind. AllRewinder::iterator rewinder; for (rewinder = m_all_rewinder.begin(); rewinder != m_all_rewinder.end(); ++rewinder) { (*rewinder)->saveTransform(); } // Then undo the rewind infos going backwards in time // -------------------------------------------------- m_is_rewinding = true; // This will go back till the first confirmed state is found before // the specified rewind ticks. int exact_rewind_ticks = m_rewind_queue.undoUntil(rewind_ticks); // Rewind the required state(s) // ---------------------------- World *world = World::getWorld(); // Now start the rewind with the full state: world->setTicks(exact_rewind_ticks); // Get the (first) full state to which we have to rewind RewindInfo *current = m_rewind_queue.getCurrent(); assert(current->isState()); // Restore states from the exact rewind time // ----------------------------------------- // A loop in case that we should split states into several smaller ones: while (current && current->getTicks() == exact_rewind_ticks && current->isState() ) { current->rewind(); m_rewind_queue.next(); current = m_rewind_queue.getCurrent(); } // Now go forward through the list of rewind infos till we reach 'now': while(world->getTimeTicks() < now_ticks ) { m_rewind_queue.replayAllEvents(world->getTimeTicks()); // Now simulate the next time step world->updateWorld(1); #undef SHOW_ROLLBACK #ifdef SHOW_ROLLBACK irr_driver->update(stk_config->ticks2Time(1)); #endif world->updateTime(1); } // while (world->getTicks() < current_ticks) // Now compute the errors which need to be visually smoothed for (rewinder = m_all_rewinder.begin(); rewinder != m_all_rewinder.end(); ++rewinder) { (*rewinder)->computeError(); } history->setReplayHistory(is_history); m_is_rewinding = false; } // rewindTo
/** Unit tests for RewindQueue. It tests: * - Sorting order of RewindInfos at the same time (i.e. state before time * before events). * - Sorting order of RewindInfos with different timestamps (and a mixture * of types). * - Special cases that triggered incorrect behaviour previously. */ void RewindQueue::unitTesting() { // Some classes need the RewindManager (to register themselves with) RewindManager::create(); auto dummy_rewinder = std::make_shared<DummyRewinder>(); // First tests: add a state first, then an event, and make // sure the state stays first RewindQueue q0; assert(q0.isEmpty()); assert(!q0.hasMoreRewindInfo()); q0.addLocalState(NULL, /*confirmed*/true, 0); assert(q0.m_all_rewind_info.front()->isState()); assert(!q0.m_all_rewind_info.front()->isEvent()); assert(q0.hasMoreRewindInfo()); assert(q0.undoUntil(0) == 0); q0.addNetworkEvent(dummy_rewinder.get(), NULL, 0); // Network events are not immediately merged assert(q0.m_all_rewind_info.size() == 1); bool needs_rewind; int rewind_ticks; int world_ticks = 0; q0.mergeNetworkData(world_ticks, &needs_rewind, &rewind_ticks); assert(q0.hasMoreRewindInfo()); assert(q0.m_all_rewind_info.size() == 2); AllRewindInfo::iterator rii = q0.m_all_rewind_info.begin(); assert((*rii)->isState()); rii++; assert((*rii)->isEvent()); // Another state must be sorted before the event: q0.addNetworkState(NULL, 0); assert(q0.hasMoreRewindInfo()); q0.mergeNetworkData(world_ticks, &needs_rewind, &rewind_ticks); assert(q0.m_all_rewind_info.size() == 3); rii = q0.m_all_rewind_info.begin(); assert((*rii)->isState()); rii++; assert((*rii)->isState()); rii++; assert((*rii)->isEvent()); // Test time base comparisons: adding an event to the end q0.addLocalEvent(dummy_rewinder.get(), NULL, true, 4); // Then adding an earlier event q0.addLocalEvent(dummy_rewinder.get(), NULL, false, 1); // rii points to the 3rd element, the ones added just now // should be elements4 and 5: rii++; assert((*rii)->getTicks()==1); rii++; assert((*rii)->getTicks()==4); // Now test inserting an event first, then the state RewindQueue q1; q1.addLocalEvent(NULL, NULL, true, 5); q1.addLocalState(NULL, true, 5); rii = q1.m_all_rewind_info.begin(); assert((*rii)->isState()); rii++; assert((*rii)->isEvent()); // Bugs seen before // ---------------- // 1) Current pointer was not reset from end of list when an event // was added and the pointer was already at end of list RewindQueue b1; b1.addLocalEvent(NULL, NULL, true, 1); b1.next(); // Should now point at end of list assert(!b1.hasMoreRewindInfo()); b1.addLocalEvent(NULL, NULL, true, 2); RewindInfo *ri = b1.getCurrent(); if (ri->getTicks() != 2) Log::fatal("RewindQueue", "ri->getTicks() != 2"); // 2) Make sure when adding an event at the same time as an existing // event, that m_current pooints to the first event, otherwise // events with same time stamp will not be handled correctly. // At this stage current points to the event at time 2 from above AllRewindInfo::iterator current_old = b1.m_current; b1.addLocalEvent(NULL, NULL, true, 2); // Make sure that current was not modified, i.e. the new event at time // 2 was added at the end of the list: if (current_old != b1.m_current) Log::fatal("RewindQueue", "current_old != b1.m_current"); // This should not trigger an exception, now current points to the // second event at the same time: b1.next(); assert(ri->getTicks() == 2); assert(ri->isEvent()); b1.next(); assert(b1.m_current == b1.m_all_rewind_info.end()); // 3) Test that if cleanupOldRewindInfo is called, it will if necessary // adjust m_current to point to the latest confirmed state. RewindQueue b2; b2.addNetworkState(NULL, 1); b2.addNetworkState(NULL, 2); b2.addNetworkState(NULL, 3); b2.mergeNetworkData(4, &needs_rewind, &rewind_ticks); assert((*b2.m_current)->getTicks() == 3); } // unitTesting