From 09688bcacdbafcd649cde5081c6e3e825136995a Mon Sep 17 00:00:00 2001 From: Mathias Gottschlag Date: Sun, 8 Feb 2009 12:19:38 +0100 Subject: [PATCH] - Partly implemented client side prediction (still unstable). - Fixed CMakeLists.txt for bullet 2.73. --- CMakeLists.txt | 2 +- engine/include/core/Entity.h | 24 +++++++++ engine/src/core/Entity.cpp | 122 ++++++++++++++++++++++++++++++++++++++----- engine/src/core/Game.cpp | 83 +++++++++++++++++++++++++++-- 4 files changed, 214 insertions(+), 17 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9c059ce..37da2e8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,5 +39,5 @@ test/src/main.cpp set(CMAKE_CXX_FLAGS "-Wall -Wextra -W -Wno-long-long -Wno-unused-parameter -I/usr/local/include/horde3d -I/usr/local/include/bullet -g") include_directories(test/include engine/include) add_executable(peaktest ${SRC}) -target_link_libraries(peaktest m Horde3D Horde3DUtils enet glfw freeimage bulletsoftbody bulletdynamics bulletcollision bulletmath) +target_link_libraries(peaktest m Horde3D Horde3DUtils enet glfw freeimage BulletSoftBody BulletDynamics BulletCollision LinearMath) diff --git a/engine/include/core/Entity.h b/engine/include/core/Entity.h index 5f548b9..243f658 100644 --- a/engine/include/core/Entity.h +++ b/engine/include/core/Entity.h @@ -67,6 +67,7 @@ namespace peak struct EntityCommand { unsigned int timestamp; + unsigned short id; Buffer data; }; @@ -85,7 +86,9 @@ namespace peak bool sendFullUpdate(Buffer *buffer); bool sendDeltaUpdate(Buffer *buffer, int time1, int time2); bool applyDeltaUpdate(Buffer *buffer, EntityState *from, EntityState *to); + //bool applyDeltaUpdate(Buffer *buffer, EntityState *state); EntityState *getState(int time); + bool checkState(EntityState *state, Buffer *updatebuffer); void discardStates(bool future, bool past); @@ -99,6 +102,7 @@ namespace peak void sendCommand(EntityCommand *command); virtual void applyCommand(EntityCommand *command); void applyNextCommand(void); + void applyCommand(int time); void setID(int id); int getID(void); @@ -107,6 +111,25 @@ namespace peak std::string getType(void); + unsigned short getLastCommandID(void) + { + return lastcommandid; + } + unsigned int getLastCommandTime(void) + { + return lastcommandtime; + } + EntityCommand *getCommand(int id) + { + std::list::iterator it = lastcommands.begin(); + while (it != lastcommands.end()) + { + if ((*it)->id == id) return *it; + it++; + } + return 0; + } + void finalize(void); protected: void add(Model *model, unsigned int flags = 0); @@ -121,6 +144,7 @@ namespace peak std::list lastcommands; unsigned int lastcommandtime; + unsigned short lastcommandid; int id; static unsigned int maxstates; diff --git a/engine/src/core/Entity.cpp b/engine/src/core/Entity.cpp index 6573e15..2194f3a 100644 --- a/engine/src/core/Entity.cpp +++ b/engine/src/core/Entity.cpp @@ -87,6 +87,7 @@ namespace peak Entity::Entity() { lastcommandtime = 0; + lastcommandid = 0; } Entity::~Entity() { @@ -291,6 +292,84 @@ namespace peak } return state; } + bool Entity::checkState(EntityState *state, Buffer *updatebuffer) + { + int bufferposition = updatebuffer->getPosition(); + state->data.setPosition(0); + // Get mask which contains the changed variables + int masksize = (variables.size() + 7) / 8; + unsigned char *mask = (unsigned char*)updatebuffer->getData(); + mask += updatebuffer->getPosition(); + updatebuffer->setPosition(masksize, true); + // Loop through the variables and set them if they got changed + for (unsigned int i = 0; i < variables.size(); i++) + { + bool changed = (mask[i / 8] & (1 << (i % 8))) != 0; + if (changed) + { + //char *dest = state->data.getData() + state->data.getPosition(); + switch (variables[i].type) + { + case EVT_Int8: + if (updatebuffer->readByte() != state->data.readByte()) + { + printf("Byte differs.\n"); + updatebuffer->setPosition(bufferposition); + return false; + } + break; + case EVT_Int16: + if (updatebuffer->readWord() != state->data.readWord()) + { + printf("Word differs.\n"); + updatebuffer->setPosition(bufferposition); + return false; + } + break; + case EVT_Int32: + case EVT_Float: + case EVT_Double: + { + int a = updatebuffer->readInt(); + int b = state->data.readInt(); + if (a != b) + { + printf("Int differs (%d vs %d).\n", a, b); + updatebuffer->setPosition(bufferposition); + return false; + } + else + printf("Int ok (%d vs %d).\n", a, b); + } + break; + default: + break; + } + } + else + { + // Skip the variable in the state buffer + switch (variables[i].type) + { + case EVT_Int8: + state->data.setPosition(1, true); + break; + case EVT_Int16: + state->data.setPosition(2, true); + break; + case EVT_Int32: + case EVT_Float: + case EVT_Double: + state->data.setPosition(4, true); + break; + default: + break; + } + } + } + updatebuffer->setPosition(bufferposition); + return true; + } void Entity::discardStates(bool future, bool past) { @@ -321,11 +400,13 @@ namespace peak bool Entity::save(void) { + printf("States: %d\n", (int)(past.size() + future.size())); if (past.size() + future.size() >= maxstates) { // Delete oldest state if (past.size() > 0) { + printf("Deleting %d.\n", (*past.rbegin())->timestamp); delete *past.rbegin(); std::list::iterator it = past.end(); it--; @@ -333,6 +414,7 @@ namespace peak } else { + printf("Deleting %d.\n", (*future.begin())->timestamp); delete *future.begin(); future.erase(past.begin()); } @@ -340,6 +422,7 @@ namespace peak // Save current state EntityState *newstate = new EntityState; saveState(newstate); + printf("Inserting %d\n", newstate->timestamp); past.push_front(newstate); return true; } @@ -375,22 +458,18 @@ namespace peak return false; // Switch to selected state restoreState(newstate); - // TODO // Rearrange states so that the lists are valid again it = past.begin(); while (it != past.end()) { if ((*it)->timestamp > time) { - std::list::iterator next = it; - next++; EntityState *state = *it; - past.erase(it); + it = past.erase(it); if (!deletefuture) - future.push_front(state); + future.push_back(state); else delete state; - it = next; continue; } it++; @@ -400,12 +479,9 @@ namespace peak { if ((*it)->timestamp <= time) { - std::list::iterator next = it; - next++; EntityState *state = *it; - future.erase(it); - past.push_back(state); - it = next; + it = future.erase(it); + past.push_front(state); continue; } it++; @@ -485,15 +561,24 @@ namespace peak void Entity::sendCommand(EntityCommand *command) { + // FIXME: Causes problems if both server and client send commands + if (!Game::get()->isServer()) + command->id = ++lastcommandid; + else if (command->id) + lastcommandid = command->id; + //command->timestamp = Game::get()->getTime(); lastcommands.push_back(command); + lastcommandtime = command->timestamp; if (!Game::get()->isServer()) { // Send command to server Buffer msg; msg.writeByte(1); msg.writeWord(getID()); + msg.writeWord(command->id); msg += command->data; - Game::get()->sendServerData(&msg, false); + Game::get()->sendServerData(&msg, true); + applyCommand(command); } } void Entity::applyCommand(EntityCommand *command) @@ -519,6 +604,19 @@ namespace peak { } } + void Entity::applyCommand(int time) + { + std::list::iterator it = lastcommands.begin(); + while (it != lastcommands.end()) + { + EntityCommand *cmd = *it; + if (cmd->timestamp == time) + { + applyCommand(cmd); + } + it++; + } + } void Entity::setID(int id) { diff --git a/engine/src/core/Game.cpp b/engine/src/core/Game.cpp index dbd1c7f..1f8bd5c 100644 --- a/engine/src/core/Game.cpp +++ b/engine/src/core/Game.cpp @@ -354,10 +354,11 @@ namespace peak int type = msg->readByte(); if (type == 1) { - if (msg->getDataSize() < 3) return; + if (msg->getDataSize() < 5) return; // Client command printf("Client command: %d bytes.\n", msg->getDataSize()); int entity = msg->readWord(); + int id = msg->readWord(); if (entity == 0) break; if (!entities[entity - 1]) @@ -365,8 +366,9 @@ namespace peak // Insert command into system // TODO: Validation EntityCommand *command = new EntityCommand; - command->data = Buffer(msg->getData() + 3, msg->getDataSize() - 3); - command->timestamp = time; + command->data = Buffer(msg->getData() + 5, msg->getDataSize() - 5); + command->id = id; + command->timestamp = getTime(); entities[entity - 1]->sendCommand(command); } delete msg; @@ -395,6 +397,8 @@ namespace peak { // Add update to buffer updatebuffer.writeWord(currententity + 1); + updatebuffer.writeWord(entities[currententity]->getLastCommandID()); + updatebuffer.writeWord(getTime() - entities[currententity]->getLastCommandTime()); if (!entities[currententity]->update(&updatebuffer)) { updatebuffer.setSize(updatebuffer.getDataSize() - 2); @@ -443,7 +447,7 @@ namespace peak { if (entities[i]) { - entities[i]->update(msecs); + entities[i]->update(20); entities[i]->save(); } } @@ -458,6 +462,77 @@ namespace peak break; if (!entities[entity - 1]) break; + int cmdid = msg->readWord(); + EntityCommand *cmd = 0; + if (cmdid != 0) + { + printf("Command: %d\n", cmdid); + cmd = entities[entity - 1]->getCommand(cmdid); + } + int cmdtime = msg->readWord(); + if (cmd) + { + cmdtime += cmd->timestamp; + printf("Command data %d ticks in the past.\n", time - cmdtime); + EntityState *state = entities[entity - 1]->getState(cmdtime); + if (!state || (time - cmdtime <= 0)) + { + if (state == 0) + { + printf("State %d not there (now: %d).\n", cmdtime, time); + } + // Force update + cmd = 0; + } + else if (!entities[entity - 1]->checkState(state, msg)) + { + entities[entity - 1]->applyDeltaUpdate(msg, 0, state); + // Reset to step to which the update refers to + for (int i = 0; i < 65535; i++) + { + if (entities[i]) + { + if (i != entity - 1) + { + entities[i]->reset(cmdtime); + } + else + { + entities[i]->reset(cmdtime, true); + } + } + } + int timedifference = time - cmdtime; + this->time = cmdtime; + // Replay last frames + for (int i = 0; i < timedifference; i++) + { + printf("."); + this->time++; + for (int i = 0; i < 65535; i++) + { + if (entities[i]) + { + entities[i]->update(20); + if (i == entity - 1) + { + entities[i]->applyCommand(this->time); + entities[i]->save(); + } + else + { + entities[i]->reset(this->time); + } + } + } + } + printf("\n"); + } + } + if (cmd || entities[entity - 1]->getLastCommandID()) + { + break; + } // Inject delta update if (!entities[entity - 1]->injectUpdate(time, msg, time == this->time)) break; -- 2.11.4.GIT