/** 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
示例#3
0
/** 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