├── Example ├── CMake ├── Rakefile ├── bin │ ├── Data │ ├── Autoload │ └── CoreData ├── Main.cpp ├── CMakeLists.txt ├── MyApp.h └── MyApp.cpp ├── CSP_messages.h ├── LICENSE ├── CSP_Client.h ├── README.md ├── CSP_Server.h ├── CSP_Server.cpp └── CSP_Client.cpp /Example/CMake: -------------------------------------------------------------------------------- 1 | E:/Urho3D/Urho3D/CMake -------------------------------------------------------------------------------- /Example/Rakefile: -------------------------------------------------------------------------------- 1 | E:/Urho3D/Urho3D/Rakefile -------------------------------------------------------------------------------- /Example/bin/Data: -------------------------------------------------------------------------------- 1 | E:/Urho3D/Urho3D/bin/Data -------------------------------------------------------------------------------- /Example/bin/Autoload: -------------------------------------------------------------------------------- 1 | E:/Urho3D/Urho3D/bin/Autoload -------------------------------------------------------------------------------- /Example/bin/CoreData: -------------------------------------------------------------------------------- 1 | E:/Urho3D/Urho3D/bin/CoreData -------------------------------------------------------------------------------- /Example/Main.cpp: -------------------------------------------------------------------------------- 1 | #include "MyApp.h" 2 | 3 | 4 | URHO3D_DEFINE_APPLICATION_MAIN(MyApp) -------------------------------------------------------------------------------- /CSP_messages.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Urho3D 4 | { 5 | /* Client Side Prediction Message IDs */ 6 | /* Client -> server */ 7 | // Custom input message to add update ID and be in sync with the update rate 8 | constexpr int MSG_CSP_INPUT = 153; 9 | /* Server -> client */ 10 | // Sends a complete snapshot of the world 11 | constexpr int MSG_CSP_STATE = 154; 12 | } 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Example/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Set CMake minimum version and CMake policy required by UrhoCommon module 2 | cmake_minimum_required (VERSION 3.2.3) 3 | if (COMMAND cmake_policy) 4 | # Libraries linked via full path no longer produce linker search paths 5 | cmake_policy (SET CMP0003 NEW) 6 | # INTERFACE_LINK_LIBRARIES defines the link interface 7 | cmake_policy (SET CMP0022 NEW) 8 | # Disallow use of the LOCATION target property - so we set to OLD as we still need it 9 | cmake_policy (SET CMP0026 OLD) 10 | # MACOSX_RPATH is enabled by default 11 | cmake_policy (SET CMP0042 NEW) 12 | # Honor the visibility properties for SHARED target types only 13 | cmake_policy (SET CMP0063 OLD) 14 | endif () 15 | 16 | # Set project name 17 | project (CSP) 18 | 19 | # Set CMake modules search path 20 | set (CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/CMake/Modules) 21 | 22 | # Include UrhoCommon.cmake module after setting project name 23 | include (UrhoCommon) 24 | 25 | # Define target name 26 | set (TARGET_NAME Main) 27 | 28 | # Define source files 29 | define_source_files () 30 | 31 | # Setup target with resource copying 32 | setup_main_executable () 33 | 34 | # Setup test cases 35 | if (URHO3D_ANGELSCRIPT) 36 | setup_test (NAME ExternalLibAS OPTIONS Scripts/12_PhysicsStressTest.as -w) 37 | endif () 38 | if (URHO3D_LUA) 39 | setup_test (NAME ExternalLibLua OPTIONS LuaScripts/12_PhysicsStressTest.lua -w) 40 | endif () 41 | -------------------------------------------------------------------------------- /CSP_Client.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CSP_messages.h" 4 | #include "StateSnapshot.h" 5 | #include 6 | #include 7 | 8 | namespace Urho3D 9 | { 10 | class Context; 11 | class Controls; 12 | class Connection; 13 | class MemoryBuffer; 14 | } 15 | 16 | using namespace Urho3D; 17 | 18 | 19 | /* 20 | Client side prediction client. 21 | 22 | - sends input to server 23 | - receive state snapshot from server and run prediction 24 | */ 25 | struct CSP_Client : Object 26 | { 27 | URHO3D_OBJECT(CSP_Client, Object); 28 | 29 | CSP_Client(Context* context); 30 | 31 | using ID = unsigned; 32 | 33 | // Register object factory and attributes. 34 | static void RegisterObject(Context* context); 35 | 36 | 37 | // Fixed timestep length 38 | float timestep = 0; 39 | 40 | Controls* prediction_controls = nullptr; 41 | 42 | // Tags the input with "id" extraData, adds it to the input buffer, and sends it to the server. 43 | void add_input(Controls& input); 44 | 45 | protected: 46 | // current client-side update ID 47 | ID id = 0; 48 | // The current recieved ID from the server 49 | ID server_id = -1; 50 | 51 | // Input buffer 52 | std::vector input_buffer; 53 | // Reusable message buffer 54 | VectorBuffer input_message; 55 | 56 | HashMap scene_snapshots; 57 | 58 | 59 | // Handle custom network messages 60 | void HandleNetworkMessage(StringHash eventType, VariantMap& eventData); 61 | 62 | // Sends the client's input to the server 63 | void send_input(Controls& controls); 64 | // read server's last received ID 65 | void read_last_id(MemoryBuffer& message); 66 | 67 | 68 | // do client-side prediction 69 | void predict(); 70 | 71 | // Re-apply all the inputs since after the current server ID to the current ID to correct the current network state. 72 | void reapply_inputs(); 73 | 74 | // Remove all the elements in the buffer which are behind the server_id, including it since it was already applied. 75 | void remove_obsolete_history(); 76 | }; 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Urho3D-CSP 2 | Urho3D Client Side Prediction Subsystem 3 | 4 | Note that this subsystem isn't polished. 5 | 6 | # Limitations 7 | Currently full game state rewinding isn't provided. 8 | For things like physics to work the whole physics world needs to be rewinded and stepped for each replayed input. 9 | It's also needed for other interactions which aren't directly trigged by input. 10 | 11 | # Instructions 12 | There are few things you need to do to use the subsystem: 13 | - Disable PhysicsWorld's interpolation for deterministic simulation. 14 | - Set the input timestep. Most likely to be the physics simulation FPS. 15 | - Set std::function to a function that applies input locally to the client/server. 16 | - Set std::function to a function that applies input provided by a client connection. 17 | - Add LOCAL server-side nodes to the CSP system. 18 | - Add input to the CSP system. Note that you need to manually apply it locally, that is the CSP system only uses it for client side prediction and server side custom input message processing for clients (not locally). 19 | 20 | Don't forget to register it as an object! 21 | ```c++ 22 | ClientSidePrediction::RegisterObject(context); 23 | ``` 24 | (and as a subsystem if you want to) 25 | 26 | Initialization example: 27 | ```c++ 28 | clientSidePrediction->timestep = 1.f / physicsWorld->GetFps(); 29 | // local input 30 | csp->apply_local_input = [&](Controls input, float timestep) { 31 | apply_input(scene->GetNode(clientObjectID_), input); 32 | }; 33 | // client input 34 | csp->apply_client_input = [&](Controls input, float timestep, Connection* connection) { 35 | apply_input(connection, input); 36 | }; 37 | ``` 38 | 39 | Adding node example: 40 | ```c++ 41 | clientSidePrediction->add_node(playerNode); 42 | ``` 43 | 44 | Adding input example: 45 | ```c++ 46 | clientSidePrediction->add_input(local_controller->controls); 47 | ``` 48 | 49 | For more detailed you can look at the example project and ClientSidePrediction header. 50 | Use CMake to build the example. It's a [downstream Urho3D project](https://urho3d.github.io/documentation/HEAD/_using_library.html). 51 | 52 | # License 53 | MIT 54 | -------------------------------------------------------------------------------- /CSP_Server.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CSP_Server.h" 4 | #include "CSP_messages.h" 5 | #include "StateSnapshot.h" 6 | #include 7 | #include 8 | #include 9 | 10 | namespace Urho3D 11 | { 12 | class Context; 13 | class Controls; 14 | class Connection; 15 | class MemoryBuffer; 16 | } 17 | 18 | using namespace Urho3D; 19 | 20 | 21 | /* 22 | Client side prediction server. 23 | 24 | - receive inputs from clients 25 | - keep track of each client's last input ID 26 | - sends last used input ID 27 | - sends state snapshot 28 | */ 29 | struct CSP_Server : Component 30 | { 31 | URHO3D_OBJECT(CSP_Server, Component); 32 | 33 | CSP_Server(Context* context); 34 | 35 | using ID = unsigned; 36 | 37 | // Register object factory and attributes. 38 | static void RegisterObject(Context* context); 39 | 40 | 41 | // Fixed timestep length 42 | float timestep = 0; 43 | 44 | 45 | // Client input ID map 46 | HashMap client_input_IDs; 47 | HashMap> client_inputs;//TODO if using queue, use a getter 48 | 49 | 50 | // Add a node to the client side prediction 51 | void add_node(Node* node); 52 | 53 | 54 | protected: 55 | // Networked scenes 56 | HashSet network_scenes; 57 | 58 | // State snapshot of each scene 59 | HashMap scene_states; 60 | HashMap scene_snapshots; 61 | 62 | // for debugging 63 | unsigned snapshots_sent = 0; 64 | 65 | // Handle custom network messages 66 | void HandleNetworkMessage(StringHash eventType, VariantMap& eventData); 67 | // Send state snapshots 68 | void HandleRenderUpdate(StringHash eventType, VariantMap& eventData); 69 | 70 | // Read input sent from the client and apply it 71 | void read_input(Connection* connection, MemoryBuffer& message); 72 | 73 | /* 74 | serialization structure: 75 | - Last input ID 76 | - state snapshot 77 | */ 78 | // Prepare state snapshot for each networked scene 79 | void prepare_state_snapshots(); 80 | // For each connection send the last received input ID and scene state snapshot 81 | void send_state_updates(); 82 | // Send a state update to a given connection 83 | void send_state_update(Connection* connection); 84 | 85 | 86 | private: 87 | // Update time interval 88 | // Update time accumulator 89 | float updateAcc_ = 0; 90 | public: 91 | //TODO same as timestep? 92 | float updateInterval_ = 1.f / 30.f; // default to 30 FPS 93 | }; 94 | -------------------------------------------------------------------------------- /Example/MyApp.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #undef TRANSPARENT 5 | #include "../CSP_Client.h" 6 | 7 | namespace Urho3D { 8 | class Node; 9 | class Scene; 10 | class UIElement; 11 | class Text; 12 | class Button; 13 | class LineEdit; 14 | class Connection; 15 | class Controls; 16 | } 17 | 18 | using namespace Urho3D; 19 | 20 | 21 | const float TOUCH_SENSITIVITY = 2.0f; 22 | 23 | 24 | struct MyApp : Application 25 | { 26 | MyApp(Context* context); 27 | 28 | SharedPtr scene; 29 | SharedPtr cameraNode; 30 | /// Camera yaw angle. 31 | float yaw_ = 0.f; 32 | /// Camera pitch angle. 33 | float pitch_ = 0.f; 34 | 35 | CSP_Client csp_client; 36 | 37 | void Setup(); 38 | void Start() override; 39 | 40 | protected: 41 | /// Mapping from client connections to controllable objects. 42 | HashMap > serverObjects_; 43 | /// Button container element. 44 | SharedPtr buttonContainer_; 45 | /// Server address line editor element. 46 | SharedPtr textEdit_; 47 | /// Connect button. 48 | SharedPtr