Browse Source

Fixed search/replaced function & variable names.
Added yet nonfunctional Network library code.
Added checksum, source file name & required package file list to Scene for networking.

Lasse Öörni 14 years ago
parent
commit
343e9052b3

+ 2 - 2
Engine/Core/Profiler.h

@@ -64,7 +64,7 @@ public:
     }
     }
     
     
     /// End timing
     /// End timing
-    void end()
+    void End()
     {
     {
         time_ += timer_.GetUSec(false);
         time_ += timer_.GetUSec(false);
     }
     }
@@ -195,7 +195,7 @@ public:
     {
     {
         if (current_ != root_)
         if (current_ != root_)
         {
         {
-            current_->end();
+            current_->End();
             current_ = current_->GetParent();
             current_ = current_->GetParent();
         }
         }
     }
     }

+ 12 - 12
Engine/Core/SharedArrayPtr.h

@@ -26,7 +26,7 @@
 #include "RefCounted.h"
 #include "RefCounted.h"
 
 
 /// Shared array pointer template class. Uses non-intrusive reference counting
 /// Shared array pointer template class. Uses non-intrusive reference counting
-template <class T> class SharedArrayPtr
+template <typename T> class SharedArrayPtr
 {
 {
 public:
 public:
     /// Construct a null shared array pointer
     /// Construct a null shared array pointer
@@ -115,7 +115,7 @@ public:
     }
     }
     
     
     /// Perform a static cast from a shared array pointer of another type
     /// Perform a static cast from a shared array pointer of another type
-    template <class U> void StaticCast(const SharedArrayPtr<U>& rhs)
+    template <typename U> void StaticCast(const SharedArrayPtr<U>& rhs)
     {
     {
         Release();
         Release();
         
         
@@ -126,7 +126,7 @@ public:
     }
     }
     
     
     /// Perform a dynatic cast from a shared array pointer of another type
     /// Perform a dynatic cast from a shared array pointer of another type
-    template <class U> void DynamicCast(const SharedArrayPtr<U>& rhs)
+    template <typename U> void DynamicCast(const SharedArrayPtr<U>& rhs)
     {
     {
         Release();
         Release();
         
         
@@ -156,7 +156,7 @@ public:
     
     
 private:
 private:
     /// Prevent direct assignment from a shared array pointer of different type
     /// Prevent direct assignment from a shared array pointer of different type
-    template <class U> SharedArrayPtr<T>& operator = (const SharedArrayPtr<U>& rhs);
+    template <typename U> SharedArrayPtr<T>& operator = (const SharedArrayPtr<U>& rhs);
     
     
     /// Release the array reference and delete it and the RefCount structure as applicable
     /// Release the array reference and delete it and the RefCount structure as applicable
     void Release()
     void Release()
@@ -188,7 +188,7 @@ private:
 };
 };
 
 
 /// Perform a static cast from one shared array pointer type to another
 /// Perform a static cast from one shared array pointer type to another
-template <class T, class U> SharedArrayPtr<T> StaticCast(const SharedArrayPtr<U>& ptr)
+template <typename T, typename U> SharedArrayPtr<T> StaticCast(const SharedArrayPtr<U>& ptr)
 {
 {
     SharedArrayPtr<T> ret;
     SharedArrayPtr<T> ret;
     ret.StaticCast(ptr);
     ret.StaticCast(ptr);
@@ -196,7 +196,7 @@ template <class T, class U> SharedArrayPtr<T> StaticCast(const SharedArrayPtr<U>
 }
 }
 
 
 /// Perform a dynamic cast from one shared array pointer type to another
 /// Perform a dynamic cast from one shared array pointer type to another
-template <class T, class U> SharedArrayPtr<T> DynamicCast(const SharedArrayPtr<U>& ptr)
+template <typename T, typename U> SharedArrayPtr<T> DynamicCast(const SharedArrayPtr<U>& ptr)
 {
 {
     SharedArrayPtr<T> ret;
     SharedArrayPtr<T> ret;
     ret.DynamicCast(ptr);
     ret.DynamicCast(ptr);
@@ -204,7 +204,7 @@ template <class T, class U> SharedArrayPtr<T> DynamicCast(const SharedArrayPtr<U
 }
 }
 
 
 /// Weak array pointer template class. Uses non-intrusive reference counting
 /// Weak array pointer template class. Uses non-intrusive reference counting
-template <class T> class WeakArrayPtr
+template <typename T> class WeakArrayPtr
 {
 {
 public:
 public:
     /// Construct a null weak array pointer
     /// Construct a null weak array pointer
@@ -322,7 +322,7 @@ public:
     }
     }
     
     
     /// Perform a static cast from a weak array pointer of another type
     /// Perform a static cast from a weak array pointer of another type
-    template <class U> void StaticCast(const WeakArrayPtr<U>& rhs)
+    template <typename U> void StaticCast(const WeakArrayPtr<U>& rhs)
     {
     {
         Release();
         Release();
         
         
@@ -333,7 +333,7 @@ public:
     }
     }
     
     
     /// Perform a dynamic cast from a weak array pointer of another type
     /// Perform a dynamic cast from a weak array pointer of another type
-    template <class U> void DynamicCast(const WeakArrayPtr<U>& rhs)
+    template <typename U> void DynamicCast(const WeakArrayPtr<U>& rhs)
     {
     {
         Release();
         Release();
         
         
@@ -363,7 +363,7 @@ public:
     
     
 private:
 private:
     /// Prevent direct assignment from a weak array pointer of different type
     /// Prevent direct assignment from a weak array pointer of different type
-    template <class U> WeakArrayPtr<T>& operator = (const WeakArrayPtr<U>& rhs);
+    template <typename U> WeakArrayPtr<T>& operator = (const WeakArrayPtr<U>& rhs);
     
     
     /// Release the weak reference. Delete the Refcount structure if the array has expired and this was the last weak reference
     /// Release the weak reference. Delete the Refcount structure if the array has expired and this was the last weak reference
     void Release()
     void Release()
@@ -388,7 +388,7 @@ private:
 };
 };
 
 
 /// Perform a static cast from one weak array pointer type to another
 /// Perform a static cast from one weak array pointer type to another
-template <class T, class U> WeakArrayPtr<T> StaticCast(const WeakArrayPtr<U>& ptr)
+template <typename T, typename U> WeakArrayPtr<T> StaticCast(const WeakArrayPtr<U>& ptr)
 {
 {
     WeakArrayPtr<T> ret;
     WeakArrayPtr<T> ret;
     ret.StaticCast(ptr);
     ret.StaticCast(ptr);
@@ -396,7 +396,7 @@ template <class T, class U> WeakArrayPtr<T> StaticCast(const WeakArrayPtr<U>& pt
 }
 }
 
 
 /// Perform a dynamic cast from one weak pointer type to another
 /// Perform a dynamic cast from one weak pointer type to another
-template <class T, class U> WeakArrayPtr<T> DynamicCast(const WeakArrayPtr<U>& ptr)
+template <typename T, typename U> WeakArrayPtr<T> DynamicCast(const WeakArrayPtr<U>& ptr)
 {
 {
     WeakArrayPtr<T> ret;
     WeakArrayPtr<T> ret;
     ret.DynamicCast(ptr);
     ret.DynamicCast(ptr);

+ 1 - 1
Engine/Core/Timer.h

@@ -100,7 +100,7 @@ public:
     void Reset();
     void Reset();
     
     
     /// Return if high-resolution timer is supported
     /// Return if high-resolution timer is supported
-    static bool Isupported() { return supported; }
+    static bool IsSupported() { return supported; }
     /// Return high-resolution timer frequency if supported
     /// Return high-resolution timer frequency if supported
     static long long GetFrequency() { return frequency; }
     static long long GetFrequency() { return frequency; }
     
     

+ 1 - 0
Engine/Engine/APITemplates.h

@@ -221,6 +221,7 @@ template <class T> void RegisterDeserializer(asIScriptEngine* engine, const char
     engine->RegisterObjectMethod(className, "String ReadLine()", asMETHODPR(T, ReadLine, (), std::string), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "String ReadLine()", asMETHODPR(T, ReadLine, (), std::string), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "uint Seek(uint)", asMETHODPR(T, Seek, (unsigned), unsigned), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "uint Seek(uint)", asMETHODPR(T, Seek, (unsigned), unsigned), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "const String& get_name() const", asMETHODPR(T, GetName, () const, const std::string&), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "const String& get_name() const", asMETHODPR(T, GetName, () const, const std::string&), asCALL_THISCALL);
+    engine->RegisterObjectMethod(className, "uint get_checksum()", asMETHODPR(T, GetChecksum, (), unsigned), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "uint get_position() const", asMETHODPR(T, GetPosition, () const, unsigned), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "uint get_position() const", asMETHODPR(T, GetPosition, () const, unsigned), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "uint get_size() const", asMETHODPR(T, GetSize, () const, unsigned), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "uint get_size() const", asMETHODPR(T, GetSize, () const, unsigned), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "bool get_eof() const", asMETHODPR(T, IsEof, () const, bool), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "bool get_eof() const", asMETHODPR(T, IsEof, () const, bool), asCALL_THISCALL);

+ 0 - 1
Engine/Engine/IOAPI.cpp

@@ -232,7 +232,6 @@ static void RegisterSerialization(asIScriptEngine* engine)
     engine->RegisterObjectMethod("File", "bool Open(const String&in, FileMode)", asMETHODPR(File, Open, (const std::string&, FileMode), bool), asCALL_THISCALL);
     engine->RegisterObjectMethod("File", "bool Open(const String&in, FileMode)", asMETHODPR(File, Open, (const std::string&, FileMode), bool), asCALL_THISCALL);
     engine->RegisterObjectMethod("File", "void Close()", asMETHOD(File, Close), asCALL_THISCALL);
     engine->RegisterObjectMethod("File", "void Close()", asMETHOD(File, Close), asCALL_THISCALL);
     engine->RegisterObjectMethod("File", "FileMode get_mode() const", asMETHOD(File, GetMode), asCALL_THISCALL);
     engine->RegisterObjectMethod("File", "FileMode get_mode() const", asMETHOD(File, GetMode), asCALL_THISCALL);
-    engine->RegisterObjectMethod("File", "uint get_checksum()", asMETHOD(File, GetChecksum), asCALL_THISCALL);
     engine->RegisterObjectMethod("File", "bool get_open()", asMETHOD(File, IsOpen), asCALL_THISCALL);
     engine->RegisterObjectMethod("File", "bool get_open()", asMETHOD(File, IsOpen), asCALL_THISCALL);
     RegisterSerializer<File>(engine, "File");
     RegisterSerializer<File>(engine, "File");
     RegisterDeserializer<File>(engine, "File");
     RegisterDeserializer<File>(engine, "File");

+ 3 - 0
Engine/Engine/NetworkAPI.cpp

@@ -89,6 +89,9 @@ void RegisterPeer(asIScriptEngine* engine)
     engine->RegisterObjectMethod("Peer", "uint get_numPackets() const", asMETHOD(Peer, GetNumPackets), asCALL_THISCALL);
     engine->RegisterObjectMethod("Peer", "uint get_numPackets() const", asMETHOD(Peer, GetNumPackets), asCALL_THISCALL);
     engine->RegisterObjectMethod("Peer", "const String& get_address() const", asMETHOD(Peer, GetAddress), asCALL_THISCALL);
     engine->RegisterObjectMethod("Peer", "const String& get_address() const", asMETHOD(Peer, GetAddress), asCALL_THISCALL);
     engine->RegisterObjectMethod("Peer", "uint16 get_port() const", asMETHOD(Peer, GetPort), asCALL_THISCALL);
     engine->RegisterObjectMethod("Peer", "uint16 get_port() const", asMETHOD(Peer, GetPort), asCALL_THISCALL);
+    
+    // Register Variant GetPtr() for Peer
+    engine->RegisterObjectMethod("Variant", "Peer@+ GetPeer() const", asFUNCTION(GetVariantPtr<Peer>), asCALL_CDECL_OBJLAST);
 }
 }
 
 
 static Network* GetNetwork()
 static Network* GetNetwork()

+ 14 - 1
Engine/Engine/SceneAPI.cpp

@@ -23,6 +23,7 @@
 
 
 #include "Precompiled.h"
 #include "Precompiled.h"
 #include "APITemplates.h"
 #include "APITemplates.h"
+#include "PackageFile.h"
 #include "Scene.h"
 #include "Scene.h"
 
 
 static void RegisterSerializable(asIScriptEngine* engine)
 static void RegisterSerializable(asIScriptEngine* engine)
@@ -67,6 +68,11 @@ static bool SceneSaveXML(File* file, Scene* ptr)
         return false;
         return false;
 }
 }
 
 
+static CScriptArray* SceneGetRequiredPackageFiles(Scene* ptr)
+{
+    return SharedPtrVectorToHandleArray<PackageFile>(ptr->GetRequiredPackageFiles(), "Array<PackageFile@>");
+}
+
 static void RegisterScene(asIScriptEngine* engine)
 static void RegisterScene(asIScriptEngine* engine)
 {
 {
     engine->RegisterEnum("NetworkMode");
     engine->RegisterEnum("NetworkMode");
@@ -82,20 +88,27 @@ static void RegisterScene(asIScriptEngine* engine)
     RegisterNode<Scene>(engine, "Scene");
     RegisterNode<Scene>(engine, "Scene");
     RegisterObjectConstructor<Scene>(engine, "Scene");
     RegisterObjectConstructor<Scene>(engine, "Scene");
     RegisterNamedObjectConstructor<Scene>(engine, "Scene");
     RegisterNamedObjectConstructor<Scene>(engine, "Scene");
+    engine->RegisterObjectMethod("Scene", "void Update(float)", asMETHOD(Scene, Update), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "bool LoadXML(File@+)", asFUNCTION(SceneLoadXML), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Scene", "bool LoadXML(File@+)", asFUNCTION(SceneLoadXML), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Scene", "bool SaveXML(File@+)", asFUNCTION(SceneSaveXML), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Scene", "bool SaveXML(File@+)", asFUNCTION(SceneSaveXML), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Scene", "bool LoadAsync(File@+)", asMETHOD(Scene, LoadAsync), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "bool LoadAsync(File@+)", asMETHOD(Scene, LoadAsync), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "bool LoadAsyncXML(File@+)", asMETHOD(Scene, LoadAsyncXML), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "bool LoadAsyncXML(File@+)", asMETHOD(Scene, LoadAsyncXML), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "void StopAsyncLoading()", asMETHOD(Scene, StopAsyncLoading), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "void StopAsyncLoading()", asMETHOD(Scene, StopAsyncLoading), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Scene", "void Clear()", asMETHOD(Scene, Clear), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Scene", "void ClearNonLocal()", asMETHOD(Scene, ClearNonLocal), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Scene", "void AddRequiredPackageFile(PackageFile@+)", asMETHOD(Scene, AddRequiredPackageFile), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Scene", "void ClearRequiredPackageFiles()", asMETHOD(Scene, ClearRequiredPackageFiles), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "Component@+ GetComponentByID(uint)", asMETHOD(Scene, GetComponentByID), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "Component@+ GetComponentByID(uint)", asMETHOD(Scene, GetComponentByID), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "Node@+ GetNodeByID(uint)", asMETHOD(Scene, GetNodeByID), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "Node@+ GetNodeByID(uint)", asMETHOD(Scene, GetNodeByID), asCALL_THISCALL);
-    engine->RegisterObjectMethod("Scene", "void Update(float)", asMETHOD(Scene, Update), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "void set_networkMode(NetworkMode)", asMETHOD(Scene, SetNetworkMode), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "void set_networkMode(NetworkMode)", asMETHOD(Scene, SetNetworkMode), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "NetworkMode get_networkMode() const", asMETHOD(Scene, GetNetworkMode), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "NetworkMode get_networkMode() const", asMETHOD(Scene, GetNetworkMode), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "void set_active(bool)", asMETHOD(Scene, SetActive), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "void set_active(bool)", asMETHOD(Scene, SetActive), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "bool get_active() const", asMETHOD(Scene, IsActive), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "bool get_active() const", asMETHOD(Scene, IsActive), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "bool get_asyncLoading() const", asMETHOD(Scene, IsAsyncLoading), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "bool get_asyncLoading() const", asMETHOD(Scene, IsAsyncLoading), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "float get_asyncProgress() const", asMETHOD(Scene, GetAsyncProgress), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "float get_asyncProgress() const", asMETHOD(Scene, GetAsyncProgress), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Scene", "uint get_checksum() const", asMETHOD(Scene, GetChecksum), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Scene", "const String& get_fileName() const", asMETHOD(Scene, GetFileName), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Scene", "Array<PackageFile@>@ get_requiredPackageFiles() const", asFUNCTION(SceneGetRequiredPackageFiles), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Node", "Scene@+ get_scene() const", asMETHOD(Node, GetScene), asCALL_THISCALL);
     engine->RegisterObjectMethod("Node", "Scene@+ get_scene() const", asMETHOD(Node, GetScene), asCALL_THISCALL);
     engine->RegisterGlobalFunction("Scene@+ get_scene()", asFUNCTION(GetScriptContextScene), asCALL_CDECL);
     engine->RegisterGlobalFunction("Scene@+ get_scene()", asFUNCTION(GetScriptContextScene), asCALL_CDECL);
     
     

+ 5 - 0
Engine/IO/Deserializer.cpp

@@ -50,6 +50,11 @@ const std::string& Deserializer::GetName() const
     return noName;
     return noName;
 }
 }
 
 
+unsigned Deserializer::GetChecksum()
+{
+    return 0;
+}
+
 int Deserializer::ReadInt()
 int Deserializer::ReadInt()
 {
 {
     int ret;
     int ret;

+ 2 - 0
Engine/IO/Deserializer.h

@@ -44,6 +44,8 @@ public:
     virtual unsigned Seek(unsigned position) = 0;
     virtual unsigned Seek(unsigned position) = 0;
     /// Return name of the stream
     /// Return name of the stream
     virtual const std::string& GetName() const;
     virtual const std::string& GetName() const;
+    /// Return a checksum if applicable
+    virtual unsigned GetChecksum();
     /// Return current position
     /// Return current position
     unsigned GetPosition() const { return position_; }
     unsigned GetPosition() const { return position_; }
     /// Return size
     /// Return size

+ 16 - 16
Engine/IO/File.cpp

@@ -219,6 +219,22 @@ unsigned File::Write(const void* data, unsigned size)
     return size;
     return size;
 }
 }
 
 
+unsigned File::GetChecksum()
+{
+    if ((offset_) || (checksum_))
+        return checksum_;
+    
+    unsigned oldPos = position_;
+    checksum_ = 0;
+    
+    Seek(0);
+    while (!IsEof())
+        checksum_ = SDBMHash(checksum_, ReadUByte());
+    
+    Seek(oldPos);
+    return checksum_;
+}
+
 void File::Close()
 void File::Close()
 {
 {
     if (handle_)
     if (handle_)
@@ -236,19 +252,3 @@ void File::SetName(const std::string& name)
 {
 {
     fileName_ = name;
     fileName_ = name;
 }
 }
-
-unsigned File::GetChecksum()
-{
-    if ((offset_) || (checksum_))
-        return checksum_;
-    
-    unsigned oldPos = position_;
-    checksum_ = 0;
-    
-    Seek(0);
-    while (!IsEof())
-        checksum_ = SDBMHash(checksum_, ReadUByte());
-    
-    Seek(oldPos);
-    return checksum_;
-}

+ 2 - 2
Engine/IO/File.h

@@ -60,6 +60,8 @@ public:
     virtual unsigned Write(const void* data, unsigned size);
     virtual unsigned Write(const void* data, unsigned size);
     /// Return the file name
     /// Return the file name
     virtual const std::string& GetName() const { return fileName_; }
     virtual const std::string& GetName() const { return fileName_; }
+    /// Return a checksum of the file contents, using the SDBM hash algorithm
+    virtual unsigned GetChecksum();
     
     
     /// Open a filesystem file. Return true if successful
     /// Open a filesystem file. Return true if successful
     bool Open(const std::string& fileName, FileMode mode = FILE_READ);
     bool Open(const std::string& fileName, FileMode mode = FILE_READ);
@@ -74,8 +76,6 @@ public:
     FileMode GetMode() const { return mode_; }
     FileMode GetMode() const { return mode_; }
     /// Return whether is open
     /// Return whether is open
     bool IsOpen() const { return handle_ != 0; }
     bool IsOpen() const { return handle_ != 0; }
-    /// Return a checksum of the file contents, using the SDBM hash algorithm
-    unsigned GetChecksum();
     
     
     /// Return the file handle
     /// Return the file handle
     void* GetHandle() const { return handle_; }
     void* GetHandle() const { return handle_; }

+ 2 - 2
Engine/Network/CMakeLists.txt

@@ -8,11 +8,11 @@ set (SOURCE_FILES ${CPP_FILES} ${H_FILES})
 
 
 # Include directories
 # Include directories
 include_directories (
 include_directories (
-    ../Core ../IO ../Math ../../ThirdParty/ENet/include
+    ../Core ../IO ../Math ../Resource ../Scene ../../ThirdParty/ENet/include
 )
 )
 
 
 # Define target & libraries to link
 # Define target & libraries to link
 add_library (${TARGET_NAME} STATIC ${SOURCE_FILES})
 add_library (${TARGET_NAME} STATIC ${SOURCE_FILES})
-target_link_libraries (${TARGET_NAME} Core ENet IO Math)
+target_link_libraries (${TARGET_NAME} Core ENet IO Math Resource Scene)
 enable_pch ()
 enable_pch ()
 finalize_lib ()
 finalize_lib ()

+ 955 - 0
Engine/Network/Client.cpp

@@ -0,0 +1,955 @@
+//
+// Urho3D Engine
+// Copyright (c) 2008-2011 Lasse Öörni
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#include "Precompiled.h"
+#include "Client.h"
+#include "Connection.h"
+#include "FileSystem.h"
+#include "Log.h"
+#include "Network.h"
+#include "NetworkEvents.h"
+#include "PackageFile.h"
+#include "ProcessUtils.h"
+#include "Profiler.h"
+#include "Protocol.h"
+#include "ProtocolEvents.h"
+#include "ResourceCache.h"
+#include "Scene.h"
+#include "SceneEvents.h"
+#include "StringUtils.h"
+
+#include "DebugNew.h"
+
+static const Controls noControls;
+static const int MIN_FILE_FRAGMENT_COUNT = 16;
+static const int MAX_FILE_FRAGMENT_COUNT = 1024;
+static const int FILE_FRAGMENT_COUNT_DELTA = 8;
+
+OBJECTTYPESTATIC(Client);
+
+Client::Client(Context* context) :
+    Object(context),
+    netFps_(30),
+    timeAcc_(0.0f),
+    frameNumber_(1)
+{
+    SubscribeToEvent(E_PEERDISCONNECTED, HANDLER(Client, HandlePeerDisconnected));
+    SubscribeToEvent(E_FILETRANSFERCOMPLETED, HANDLER(Client, HandleFileTransferCompleted));
+    SubscribeToEvent(E_FILETRANSFERFAILED, HANDLER(Client, HandleFileTransferFailed));
+    SubscribeToEvent(E_ASYNCLOADFINISHED, HANDLER(Client, HandleAsyncLoadFinished));
+}
+
+Client::~Client()
+{
+}
+
+void Client::SetScene(Scene* scene)
+{
+    scene_ = scene;
+}
+
+void Client::SetDownloadDirectory(const std::string& path)
+{
+    downloadDirectory_ = AddTrailingSlash(path);
+}
+
+bool Client::Connect(const std::string& address, unsigned short port, const VariantMap& loginData)
+{
+    if (!scene_)
+        return false;
+    
+    // Make sure any previous async loading is stopped
+    scene_->StopAsyncLoading();
+    
+    Disconnect();
+    
+    Peer* peer = GetSubsystem<Network>()->Connect(address, port);
+    if (!peer)
+        return false;
+    
+    serverConnection_ = new Connection(context_, peer);
+    frameNumber_ = 1;
+    pendingLoginData_ = loginData;
+    return true;
+}
+
+void Client::Disconnect()
+{
+    if (serverConnection_)
+    {
+        serverConnection_->Disconnect();
+        serverConnection_.Reset();
+    }
+    
+    // Stop async loading if was in progress
+    if (!scene_)
+        scene_->StopAsyncLoading();
+    
+    pendingDownloads_.clear();
+    fileTransfers_.clear();
+    sceneInfo_ = SceneInfo();
+}
+
+void Client::SetControls(const Controls& controls)
+{
+    if (serverConnection_)
+        serverConnection_->SetControls(controls);
+}
+
+void Client::SetPosition(const Vector3& position)
+{
+    if (serverConnection_)
+        serverConnection_->SetPosition(position);
+}
+
+void Client::Update(float timeStep)
+{
+    PROFILE(UpdateClient);
+    
+    // Process connection (assume that Engine has updated Network, so we do not do that here)
+    if (serverConnection_)
+    {
+        VectorBuffer packet;
+        
+        // Process reliable packets first, then unreliable
+        while (serverConnection_->ReceiveReliable(packet))
+            HandleReliablePacket(packet);
+        
+        while (serverConnection_->ReceiveUnreliable(packet))
+            HandleServerUpdate(packet);
+        
+        // Update own simulation of scene if connected
+        if (serverConnection_->GetScene() == scene_)
+            scene_->Update(timeStep);
+    }
+    
+    // Send update if enough time passed
+    float netPeriod = 1.0f / netFps_;
+    timeAcc_ += timeStep;
+    if (timeAcc_ >= netPeriod)
+    {
+        // If multiple updates have accumulated because of a slow frame, send just one
+        while (timeAcc_ >= netPeriod)
+            timeAcc_ -= netPeriod;
+        ++frameNumber_;
+        // We have a policy that framenumber 0 means "frame never received", so loop back to 1
+        if (!frameNumber_)
+            ++frameNumber_;
+        SendClientUpdate();
+    }
+}
+
+bool Client::IsConnected() const
+{
+    return (serverConnection_) && (serverConnection_->IsConnected()) && (serverConnection_->HasChallenge());
+}
+
+bool Client::IsJoinPending() const
+{
+    return ((IsConnected()) && (serverConnection_->GetJoinState() == JS_PREPARESCENE));
+}
+
+bool Client::IsJoined() const
+{
+    return ((IsConnected()) && (serverConnection_->GetJoinState() > JS_PREPARESCENE));
+}
+
+const Controls& Client::GetControls() const
+{
+    if (!serverConnection_)
+        return noControls;
+    return serverConnection_->GetControls();
+}
+
+const Vector3& Client::GetPosition() const
+{
+    if (!serverConnection_)
+        return Vector3::ZERO;
+    return serverConnection_->GetPosition();
+}
+
+std::string Client::GetFileTransferStatus() const
+{
+    std::string ret;
+    
+    for (std::map<StringHash, FileTransfer>::const_iterator i = fileTransfers_.begin(); i != fileTransfers_.end(); ++i)
+    {
+        std::string line = i->second.fileName_ + " " + ToString(i->second.bytesReceived_) + "/" + ToString(i->second.size_) 
+            + " (" + ToString((int)(((float)i->second.bytesReceived_ / (float)i->second.size_) * 100.0f + 0.5f)) + "%)\n";
+        ret += line;
+    }
+    
+    return ret;
+}
+
+void Client::HandlePeerDisconnected(StringHash eventType, VariantMap& eventData)
+{
+    using namespace PeerDisconnected;
+    
+    Peer* peerPtr = static_cast<Peer*>(eventData[P_PEER].GetPtr());
+    if (peerPtr->GetType() != PEER_SERVER)
+        return;
+    
+    if (serverConnection_)
+    {
+        if (serverConnection_->GetPeer() == peerPtr)
+        {
+            serverConnection_->LeftScene();
+            serverConnection_.Reset();
+            pendingDownloads_.clear();
+            fileTransfers_.clear();
+            sceneInfo_ = SceneInfo();
+        }
+    }
+}
+
+void Client::HandleFileTransferCompleted(StringHash eventType, VariantMap& eventData)
+{
+    using namespace FileTransferCompleted;
+    
+    std::string fileName = eventData[P_FILENAME].GetString();
+    if (pendingDownloads_.find(fileName) != pendingDownloads_.end())
+    {
+        pendingDownloads_.erase(fileName);
+        
+        // Add the package to the resource cache
+        SharedPtr<PackageFile> package(new PackageFile(context_));
+        if (!package->Open(eventData[P_FULLPATH].GetString()))
+        {
+            JoinFailed("Could not open downloaded package file " + fileName);
+            return;
+        }
+        // Add the package as first in case it overrides something in the default files
+        GetSubsystem<ResourceCache>()->AddPackageFile(package, true);
+        
+        // If this was the last required download, can now join scene
+        if ((pendingDownloads_.empty()) && (IsJoinPending()))
+            SetupScene();
+    }
+}
+
+void Client::HandleFileTransferFailed(StringHash eventType, VariantMap& eventData)
+{
+    using namespace FileTransferFailed;
+    
+    std::string fileName = eventData[P_FILENAME].GetString();
+    if (pendingDownloads_.find(fileName) != pendingDownloads_.end())
+        JoinFailed("Failed to transfer file " + fileName);
+}
+
+void Client::HandleAsyncLoadFinished(StringHash eventType, VariantMap& eventData)
+{
+    if ((!scene_) || (!serverConnection_))
+        return;
+    
+    using namespace AsyncLoadFinished;
+    
+    // If it is the scene used for networking, send join packet now
+    if ((eventData[P_SCENE].GetPtr() == (void*)scene_) && (serverConnection_->GetJoinState() == JS_LOADSCENE))
+        SendJoinScene();
+}
+
+void Client::HandleReliablePacket(VectorBuffer& packet)
+{
+    unsigned char msgID = 0;
+    
+    msgID = packet.ReadUByte();
+    
+    switch (msgID)
+    {
+    case MSG_CHALLENGE:
+        HandleChallenge(packet);
+        break;
+        
+    case MSG_SCENEINFO:
+        HandleSceneInfo(packet);
+        break;
+        
+    case MSG_TRANSFERDATA:
+        HandleTransferData(packet);
+        break;
+        
+    case MSG_TRANSFERFAILED:
+        HandleTransferFailed(packet);
+        break;
+        
+    case MSG_JOINREPLY:
+        HandleJoinReply(packet);
+        break;
+        
+    case MSG_FULLUPDATE:
+        HandleFullUpdate(packet);
+        break;
+        
+    default:
+        LOGERROR("Unknown message ID " + ToString((int)msgID) + " received from server");
+        break;
+    }
+}
+
+void Client::HandleChallenge(VectorBuffer& packet)
+{
+    serverConnection_->SetChallenge(packet.ReadUInt());
+    
+    // Send login packet as a response
+    VectorBuffer replyPacket;
+    replyPacket.WriteUByte(MSG_LOGIN);
+    replyPacket.WriteVariantMap(pendingLoginData_);
+    serverConnection_->SendReliable(replyPacket);
+}
+
+void Client::HandleSceneInfo(VectorBuffer& packet)
+{
+    if (!scene_)
+        return;
+    
+    // Stop all previous loading, associate the scene with the connection
+    scene_->StopAsyncLoading();
+    serverConnection_->SetScene(scene_);
+    
+    // Read scene name, number of users and update rate
+    std::string sceneName = packet.ReadString();
+    sceneInfo_.name_ = sceneName;
+    sceneInfo_.numUsers_ = packet.ReadVLE();
+    sceneInfo_.netFps_ = packet.ReadInt();
+    
+    // Read source file name & required packages
+    sceneInfo_.fileName_ = packet.ReadString();
+    unsigned numPackages = packet.ReadVLE();
+    sceneInfo_.requiredPackages_.clear();
+    for (unsigned i = 0; i < numPackages; ++i)
+    {
+        PackageInfo package;
+        package.name_ = packet.ReadString();
+        package.size_ = packet.ReadUInt();
+        package.checksum_ = packet.ReadUInt();
+        sceneInfo_.requiredPackages_.push_back(package);
+    }
+    
+    // Check need for downloads: if none, can join immediately
+    if (!CheckPackages())
+        SetupScene();
+}
+
+void Client::HandleTransferData(VectorBuffer& packet)
+{
+    StringHash nameHash = packet.ReadStringHash();
+    std::map<StringHash, FileTransfer>::iterator i = fileTransfers_.find(nameHash);
+    if (i == fileTransfers_.end())
+    {
+        LOGDEBUG("Received fragment for nonexisting file transfer " + ToString(nameHash));
+        return;
+    }
+    FileTransfer& transfer = i->second;
+    
+    unsigned index = packet.ReadVLE();
+    if (transfer.fragmentsReceived_ != index)
+    {
+        LOGERROR("Received unexpected fragment for file " + ToString(nameHash) + ", stopping transfer");
+        
+        using namespace FileTransferFailed;
+        
+        VariantMap eventData;
+        eventData[P_FILENAME] = transfer.fileName_;
+        eventData[P_REASON] = "Unexpected fragment";
+        SendEvent(E_FILETRANSFERFAILED, eventData);
+        
+        fileTransfers_.erase(i);
+        return;
+    }
+    
+    transfer.fragmentsReceived_ = index + 1; // We receive fragments in order
+    unsigned dataSize = packet.GetSize() - packet.GetPosition();
+    transfer.file_->Write(packet.GetData() + packet.GetPosition(), dataSize);
+    transfer.bytesReceived_ += dataSize;
+    
+    if (transfer.fragmentsReceived_ == transfer.numFragments_)
+    {
+        if (transfer.bytesReceived_ != transfer.size_)
+        {
+            LOGERROR("Transfer of file " + transfer.fileName_ + " finished, expected " + ToString(transfer.size_) +
+                " bytes but got " + ToString(transfer.bytesReceived_));
+            
+            using namespace FileTransferFailed;
+            
+            VariantMap eventData;
+            eventData[P_FILENAME] = transfer.fileName_;
+            eventData[P_REASON] = "Unexpected file size";
+            SendEvent(E_FILETRANSFERFAILED, eventData);
+        }
+        else
+        {
+            float totalTime = transfer.receiveTimer_.GetMSec(true) * 0.001f;
+            LOGINFO("Transfer of file " + transfer.fileName_ + " completed in " +
+                ToString(totalTime) + " seconds, speed " + ToString(transfer.size_ / totalTime) + " bytes/sec");
+            using namespace FileTransferCompleted;
+            
+            VariantMap eventData;
+            eventData[P_FILENAME] = transfer.fileName_;
+            eventData[P_FULLPATH] = transfer.file_->GetName();
+            
+            // Others might try to use the file as a response to the event, so close it first
+            transfer.file_.Reset();
+            
+            SendEvent(E_FILETRANSFERCOMPLETED, eventData);
+        }
+        
+        fileTransfers_.erase(i);
+        return;
+    }
+    
+    // If current batch was finished, fire off the next
+    if (transfer.fragmentsReceived_ == transfer.batchStart_ + transfer.batchSize_)
+    {
+        transfer.batchStart_ = transfer.fragmentsReceived_;
+        float batchTime = transfer.batchTimer_.GetMSec(true) * 0.001f;
+        float newDataRate = transfer.batchSize_ * FILE_FRAGMENT_SIZE / batchTime;
+        LOGDEBUG("Received " + ToString(transfer.batchSize_) + " fragments in " + ToString(batchTime) + " seconds");
+        
+        // If this was the first batch, can not yet estimate speed, so go up in batch size
+        if (!transfer.lastBatchSize_)
+        {
+            transfer.lastBatchSize_ = transfer.batchSize_;
+            transfer.lastDataRate_ = newDataRate;
+            transfer.batchSize_ += FILE_FRAGMENT_COUNT_DELTA;
+        }
+        else
+        {
+            bool goUp = true;
+            // Go down in batch size if last batch was smaller and had better data rate
+            if ((transfer.lastBatchSize_ < transfer.batchSize_) && (transfer.lastDataRate_ > newDataRate))
+                goUp = false;
+            
+            transfer.lastBatchSize_ = transfer.batchSize_;
+            transfer.lastDataRate_ = newDataRate;
+            
+            if (goUp)
+                transfer.batchSize_ += FILE_FRAGMENT_COUNT_DELTA;
+            else
+                transfer.batchSize_ -= FILE_FRAGMENT_COUNT_DELTA;
+            
+            transfer.batchSize_ = Clamp((int)transfer.batchSize_, MIN_FILE_FRAGMENT_COUNT, MAX_FILE_FRAGMENT_COUNT);
+        }
+        
+        VectorBuffer packet;
+        packet.WriteUByte(MSG_REQUESTFILE);
+        packet.WriteStringHash(i->first);
+        packet.WriteVLE(transfer.batchStart_);
+        packet.WriteVLE(transfer.batchSize_);
+        serverConnection_->SendReliable(packet);
+    }
+}
+
+void Client::HandleTransferFailed(VectorBuffer& packet)
+{
+    StringHash nameHash = packet.ReadStringHash();
+    std::string reason = packet.ReadString();
+    
+    std::map<StringHash, FileTransfer>::iterator i = fileTransfers_.find(nameHash);
+    if (i == fileTransfers_.end())
+    {
+        LOGDEBUG("Received fail for nonexisting file transfer " + ToString(nameHash));
+        return;
+    }
+    
+    std::string errorMsg = "Transfer of file " + ToString(nameHash) + " failed: " + reason;
+    LOGINFO(errorMsg);
+    
+    using namespace FileTransferFailed;
+    
+    VariantMap eventData;
+    eventData[P_FILENAME] = i->second.fileName_;
+    eventData[P_REASON] = reason;
+    SendEvent(E_FILETRANSFERFAILED, eventData);
+    
+    fileTransfers_.erase(i);
+}
+
+void Client::HandleJoinReply(VectorBuffer& packet)
+{
+    if (!scene_)
+        return;
+    
+    bool success = packet.ReadBool();
+    if (success)
+    {
+        serverConnection_->JoinedScene();
+        
+        LOGINFO("Joined scene " + scene_->GetName());
+        
+        SendEvent(E_JOINEDSCENE);
+    }
+    else
+    {
+        std::string reason = packet.ReadString();
+        
+        serverConnection_->LeftScene();
+        pendingDownloads_.clear();
+        fileTransfers_.clear();
+        
+        JoinFailed(reason);
+    }
+}
+
+void Client::HandleFullUpdate(VectorBuffer& packet)
+{
+    HandleServerUpdate(packet, true);
+}
+
+void Client::HandleServerUpdate(VectorBuffer& packet, bool initial)
+{
+    if (!IsJoined())
+        return;
+    
+    // Read frame numbers
+    unsigned short lastFrameNumber = packet.ReadUShort();
+    unsigned short lastFrameAck = packet.ReadUShort();
+    
+    if (!initial)
+    {
+        // Check that this packet is not older than the last received (overlap may occur when we transition
+        // between a reliable full update and unreliable delta updates)
+        if (!CheckFrameNumber(lastFrameNumber, serverConnection_->GetFrameNumber()))
+            return;
+        //LOGDEBUG("Delta: " + ToString(packet.GetSize()));
+    }
+    else
+    {
+        // If initial/full update, remove all old non-local nodes
+        scene_->ClearNonLocal();
+        LOGDEBUG("Initial scene: " + ToString(packet.GetSize()) + " bytes");
+    }
+    
+    serverConnection_->SetFrameNumbers(lastFrameNumber, lastFrameAck);
+    serverConnection_->UpdateRoundTripTime(netFps_, frameNumber_);
+    
+    unsigned short previousEventFrameNumber = serverConnection_->GetEventFrameNumber();
+    
+    std::set<unsigned> updatedNodes;
+    std::set<unsigned> updatedComponents;
+    
+    // Read the actual scene update messages
+    while (!packet.IsEof())
+    {
+        unsigned char msgID = packet.ReadUByte();
+        switch (msgID & 0x0f)
+        {
+        case MSG_REMOTEEVENT:
+            {
+                RemoteEvent newEvent;
+                newEvent.Read(packet, false);
+                if (serverConnection_->CheckRemoteEventFrame(newEvent, previousEventFrameNumber))
+                    newEvent.Dispatch(serverConnection_, scene_);
+            }
+            break;
+            
+        case MSG_REMOTENODEEVENT:
+            {
+                RemoteEvent newEvent;
+                newEvent.Read(packet, true);
+                if (serverConnection_->CheckRemoteEventFrame(newEvent, previousEventFrameNumber))
+                    newEvent.Dispatch(serverConnection_, scene_);
+            }
+            break;
+            
+        default:
+            LOGWARNING("Unknown message ID " + ToString((int)msgID) + " received from server");
+            packet.Seek(packet.GetSize()); // Break loop
+            break;
+        }
+    }
+    
+    // Perform post network update for nodes & components
+    
+    // If initial update, send back ack
+    if (initial)
+    {
+        VectorBuffer replyPacket;
+        replyPacket.WriteUByte(MSG_FULLUPDATEACK);
+        replyPacket.WriteUShort(frameNumber_);
+        replyPacket.WriteUShort(serverConnection_->GetFrameNumber());
+        serverConnection_->SendReliable(replyPacket);
+        serverConnection_->SetJoinState(JS_SENDDELTAS);
+    }
+    
+    // Remove acked controls
+    serverConnection_->PurgeAckedControls();
+    
+    // Send notification of the server update
+    using namespace ServerUpdate;
+    
+    VariantMap eventData;
+    eventData[P_SCENE] = (void*)scene_.GetPtr();
+    SendEvent(E_SERVERUPDATE, eventData);
+}
+
+unsigned Client::CheckPackages()
+{
+    pendingDownloads_.clear();
+    
+    // To avoid resource version conflicts and to keep the amount of open packages reasonable, remove all existing
+    // downloaded packages from the resource cache first
+    std::vector<std::string> downloadedPackages;
+    std::vector<SharedPtr<PackageFile> > registeredPackages = GetSubsystem<ResourceCache>()->GetPackageFiles();
+    GetSubsystem<FileSystem>()->ScanDir(downloadedPackages, downloadDirectory_, "*.pak", SCAN_FILES, false);
+    
+    for (std::vector<SharedPtr<PackageFile> >::iterator i = registeredPackages.begin(); i != registeredPackages.end();)
+    {
+        if ((*i)->GetName().find(downloadDirectory_) != std::string::npos)
+        {
+            GetSubsystem<ResourceCache>()->RemovePackageFile(*i);
+            i = registeredPackages.erase(i);
+        }
+        else
+            ++i;
+    }
+    
+    for (unsigned i = 0; i < sceneInfo_.requiredPackages_.size(); ++i)
+    {
+        const PackageInfo& required = sceneInfo_.requiredPackages_[i];
+        std::string requiredName = GetFileName(required.name_);
+        bool found = false;
+        
+        // Check both already registered packages, and existing downloads
+        for (unsigned j = 0; j < registeredPackages.size(); ++j)
+        {
+            PackageFile* package = registeredPackages[i];
+            std::string name = GetFileName(package->GetName());
+            if ((name.find(requiredName) != std::string::npos) && (package->GetTotalSize() == required.size_) &&
+                (package->GetChecksum() == required.checksum_))
+            {
+                found = true;
+                break;
+            }
+        }
+        
+        if (!found)
+        {
+            for (unsigned j = 0; j < downloadedPackages.size(); ++j)
+            {
+                // Downloaded packages are encoded as filename_checksum.pak, so check if the filename contains the required name
+                std::string name = GetFileName(downloadedPackages[i]);
+                if (name.find(requiredName) != std::string::npos)
+                {
+                    SharedPtr<PackageFile> file(new PackageFile(context_));
+                    file->Open(downloadDirectory_ + downloadedPackages[i]);
+                    if ((file->GetTotalSize() == required.size_) && (file->GetChecksum() == required.checksum_))
+                    {
+                        // Add the package as first in case it overrides something in the default files
+                        GetSubsystem<ResourceCache>()->AddPackageFile(file, true);
+                        found = true;
+                        break;
+                    }
+                }
+            }
+        }
+        
+        if (!found)
+        {
+            // If not found, initiate the download
+            if (!RequestFile(required.name_, required.size_, required.checksum_))
+            {
+                JoinFailed("Failed to initiate transfer for file " + required.name_);
+                return M_MAX_UNSIGNED; // Return nonzero to prevent immediate join attempt
+            }
+            pendingDownloads_.insert(required.name_);
+        }
+    }
+    
+    return pendingDownloads_.size();
+}
+
+bool Client::RequestFile(const std::string& fileName, unsigned size, unsigned checksum)
+{
+    StringHash nameHash(fileName);
+    if (fileTransfers_.find(nameHash) != fileTransfers_.end())
+        return true; // Already initiated
+    
+    FileTransfer newTransfer;
+    // Append checksum to download name, so that we can have several versions of a package
+    std::string destName = GetFileName(fileName) + "_" + ToStringHex(checksum) + GetExtension(fileName);
+    newTransfer.file_ = new File(context_, downloadDirectory_ + destName, FILE_WRITE);
+    if (!newTransfer.file_->IsOpen())
+        return false;
+    
+    newTransfer.fileName_ = fileName;
+    newTransfer.size_ = size;
+    newTransfer.checksum_ = checksum;
+    newTransfer.numFragments_ = (size - 1) / FILE_FRAGMENT_SIZE + 1;
+    newTransfer.batchTimer_.Reset();
+    newTransfer.receiveTimer_.Reset();
+    newTransfer.batchSize_ = MIN_FILE_FRAGMENT_COUNT;
+    
+    VectorBuffer packet;
+    packet.WriteUByte(MSG_REQUESTFILE);
+    packet.WriteStringHash(nameHash);
+    packet.WriteVLE(newTransfer.batchStart_);
+    packet.WriteVLE(newTransfer.batchSize_);
+    serverConnection_->SendReliable(packet);
+    
+    fileTransfers_[nameHash] = newTransfer;
+    LOGINFO("Started transfer of file " + fileName + ", " + ToString(size) + " bytes");
+    return true;
+}
+
+void Client::SetupScene()
+{
+    netFps_ = sceneInfo_.netFps_;
+    timeAcc_ = 0.0f;
+    
+    // Setup the scene
+    // If no filename, just empty it
+    if (sceneInfo_.fileName_.empty())
+    {
+        scene_->SetName(sceneInfo_.name_);
+        scene_->Clear();
+        SendJoinScene();
+    }
+    else
+    {
+        SharedPtr<File> sceneFile = GetSubsystem<ResourceCache>()->GetFile(sceneInfo_.fileName_);
+        // Support either binary or XML format scene
+        if (GetExtension(sceneInfo_.fileName_) == ".xml")
+            scene_->LoadAsyncXML(sceneFile);
+        else
+            scene_->LoadAsync(sceneFile);
+        
+        // Check if scene started loading successfully
+        if (scene_->IsAsyncLoading())
+            serverConnection_->SetJoinState(JS_LOADSCENE);
+        else
+            JoinFailed("Failed to load scene " + sceneInfo_.fileName_);
+    }
+}
+
+void Client::SendJoinScene()
+{
+    if ((!scene_) || (!serverConnection_))
+        return;
+    
+    VectorBuffer packet;
+    packet.WriteUByte(MSG_JOINSCENE);
+    packet.WriteUInt(scene_->GetChecksum());
+    serverConnection_->SendReliable(packet);
+}
+
+void Client::JoinFailed(const std::string& reason)
+{
+    LOGINFO("Failed to join scene, reason: " + reason);
+    
+    using namespace JoinSceneFailed;
+    
+    VariantMap eventData;
+    eventData[P_REASON] = reason;
+    SendEvent(E_JOINSCENEFAILED);
+}
+
+void Client::SendClientUpdate()
+{
+    if (!IsJoined())
+    {
+        // If we are not connected but remote events have been queued, clear them
+        serverConnection_->ClearRemoteEvents();
+        return;
+    }
+    
+    // Request updated controls from the application
+    using namespace ControlsUpdate;
+    
+    VariantMap eventData;
+    eventData[P_SCENE] = (void*)scene_.GetPtr();
+    SendEvent(E_CONTROLSUPDATE, eventData);
+    
+    // Purge acked and expired remote events
+    serverConnection_->PurgeAckedRemoteEvents(frameNumber_);
+    
+    VectorBuffer packet;
+    packet.WriteUShort(frameNumber_);
+    packet.WriteUShort(serverConnection_->GetFrameNumber());
+    
+    // Write controls and position
+    const Controls& controls = serverConnection_->GetControls();
+    packet.WriteUByte(MSG_CONTROLS);
+    packet.WriteUInt(controls.buttons_);
+    packet.WriteFloat(controls.yaw_);
+    packet.WriteFloat(controls.pitch_);
+    packet.WriteVariantMap(controls.extraData_);
+    packet.WriteVector3(serverConnection_->GetPosition());
+    
+    // Remember the controls if prediction & replay is needed
+    serverConnection_->AddUnackedControls(frameNumber_, controls);
+    
+    // Append unacked remote events
+    const std::vector<RemoteEvent>& unackedEvents = serverConnection_->GetUnackedRemoteEvents();
+    for (std::vector<RemoteEvent>::const_iterator i = unackedEvents.begin(); i != unackedEvents.end(); ++i)
+    {
+        packet.WriteUByte(i->nodeID_ ? MSG_REMOTENODEEVENT : MSG_REMOTEEVENT);
+        i->Write(packet);
+    }
+    
+    serverConnection_->SendUnreliable(packet);
+}
+
+void Client::ReadNetUpdateBlock(Deserializer& source, unsigned char msgID, std::set<unsigned>& updatedNodes, std::set<unsigned>& updatedComponents)
+{
+    /*
+    unsigned id = source.ReadUShort();
+    Node* node = scene_->GetNodeByID(id);
+    
+    switch (msgID & 0x0f)
+    {
+    case MSG_CREATEENTITY:
+        {
+            // Node may already exist if server sends the create many times. But data may have changed
+            std::string name = source.ReadString();
+            if (!node)
+                node = scene_->createNode(id, name);
+            
+            unsigned char netFlags = source.ReadUByte();
+            node->setNetFlags(netFlags);
+            if (netFlags & NET_OWNER)
+                node->setOwner(serverConnection_);
+            else
+                node->setOwner(0);
+            node->setGroupFlags(source.ReadVLE());
+            
+            node->setProperties(source.ReadVariantMap(), true);
+            
+            std::set<Component*> extraComponents;
+            extraComponents.clear();
+            const std::vector<SharedPtr<Component> >& components = node->GetComponents();
+            for (std::vector<SharedPtr<Component> >::const_iterator i = components.begin(); i != components.end(); ++i)
+                extraComponents.insert(*i);
+            
+            unsigned nucomponents_ = source.ReadVLE();
+            for (unsigned i = 0; i < nucomponents_; ++i)
+            {
+                ShortStringHash type = source.ReadShortStringHash();
+                std::string name = source.ReadString();
+                // We might apply the same update multiple times, so check if the component already exists
+                Component* component = node->GetComponent(type, name);
+                bool newComponent = false;
+                if (!component)
+                {
+                    component = node->createComponent(type, name);
+                    newComponent = true;
+                }
+                component->setNetFlags(source.ReadUByte());
+                component->readNetUpdate(source, GetSubsystem<ResourceCache>(), info);
+                // If component is new, finish interpolation immediately
+                if (newComponent)
+                    component->interpolate(true);
+                
+                extraComponents.erase(component);
+            }
+            
+            // Now check if the node has extra components it should not, and remove them
+            for (std::set<Component*>::iterator i = extraComponents.begin(); i != extraComponents.end(); ++i)
+                node->removeComponent(*i);
+            
+            updatedNodes.insert(id);
+        }
+        break;
+        
+    case MSG_REMOVEENTITY:
+        if (node)
+            scene_->removeNode(id);
+        break;
+        
+    case MSG_UPDATEENTITY:
+        {
+            // Node should exist, but if it does not, create it now to not desync the stream
+            if (!node)
+            {
+                LOGERROR("Update to nonexisting node " + ToString(id));
+                node = scene_->createNode(id);
+            }
+            if (msgID & UPD_PROPERTIES)
+            {
+                unsigned numProperties = source.ReadVLE();
+                for (unsigned i = 0; i < numProperties; ++i)
+                {
+                    ShortStringHash key = source.ReadShortStringHash();
+                    Variant value = source.ReadVariant();
+                    node->setProperty(key, value, true);
+                }
+            }
+            if (msgID & UPD_NEWCOMPONENTS)
+            {
+                unsigned nucomponents_ = source.ReadVLE();
+                for (unsigned i = 0; i < nucomponents_; ++i)
+                {
+                    ShortStringHash type = source.ReadShortStringHash();
+                    std::string name = source.ReadString();
+                    // We might apply the same update multiple times, so check if the component already exists
+                    Component* component = node->GetComponent(type, name);
+                    bool newComponent = false;
+                    if (!component)
+                    {
+                        component = node->createComponent(type, name);
+                        newComponent = true;
+                    }
+                    component->setNetFlags(source.ReadUByte());
+                    component->readNetUpdate(source, GetSubsystem<ResourceCache>(), info);
+                    // If component is new, finish interpolation immediately
+                    if (newComponent)
+                        component->interpolate(true);
+                }
+            }
+            if (msgID & UPD_REMOVECOMPONENTS)
+            {
+                unsigned nucomponents_ = source.ReadVLE();
+                for (unsigned i = 0; i < nucomponents_; ++i)
+                {
+                    Component* component = node->GetComponent(source.ReadShortStringHash().mData);
+                    if (component)
+                        node->removeComponent(component);
+                }
+            }
+            if (msgID & UPD_UPDATECOMPONENTS)
+            {
+                unsigned nucomponents_ = source.ReadVLE();
+                for (unsigned i = 0; i < nucomponents_; ++i)
+                {
+                    ShortStringHash combinedHash = source.ReadShortStringHash();
+                    Component* component = node->GetComponent(combinedHash.mData);
+                    if (component)
+                        component->readNetUpdate(source, GetSubsystem<ResourceCache>(), info);
+                    else
+                    {
+                        // Component should exist, but if it does not, create it now to not desync the stream
+                        // Note that we only have the combined hash to go on with
+                        LOGERROR("Update to nonexisting component " + ToString(combinedHash) + " in node " +
+                            node->GetName());
+                        component = node->createComponent(combinedHash);
+                        component->readNetUpdate(source, GetSubsystem<ResourceCache>(), info);
+                        component->interpolate(true);
+                    }
+                }
+            }
+            updatedNodes.insert(id);
+        }
+        break;
+    }
+    */
+}

+ 230 - 0
Engine/Network/Client.h

@@ -0,0 +1,230 @@
+//
+// Urho3D Engine
+// Copyright (c) 2008-2011 Lasse Öörni
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+#include "Controls.h"
+#include "File.h"
+#include "Object.h"
+#include "Timer.h"
+#include "VectorBuffer.h"
+
+#include <map>
+#include <set>
+
+class Connection;
+class Network;
+class ResourceCache;
+class Scene;
+
+/// Downloadable package file description
+struct PackageInfo
+{
+    /// File name
+    std::string name_;
+    /// File size
+    unsigned size_;
+    /// Data checksum
+    unsigned checksum_;
+};
+
+/// Server scene description
+struct SceneInfo
+{
+    /// Construct with default values
+    SceneInfo() :
+        numUsers_(0),
+        netFps_(0)
+    {
+    }
+    
+    /// Scene name
+    std::string name_;
+    /// Current number of users in scene
+    unsigned numUsers_;
+    /// Network updates per second
+    int netFps_;
+    /// Scene file name
+    std::string fileName_;
+    /// Required package files
+    std::vector<PackageInfo> requiredPackages_;
+};
+
+/// An ongoing download from the server
+struct FileTransfer
+{
+    /// Construct with default values
+    FileTransfer() :
+        fragmentsReceived_(0),
+        bytesReceived_(0),
+        batchStart_(0),
+        batchSize_(0),
+        lastBatchSize_(0),
+        lastDataRate_(0.0f)
+    {
+    }
+    
+    /// File used to store the download
+    SharedPtr<File> file_;
+    /// File name
+    std::string fileName_;
+    /// File size
+    unsigned size_;
+    /// Data checksum
+    unsigned checksum_;
+    /// Total number of fragments
+    unsigned numFragments_;
+    /// Fragments received so far
+    unsigned fragmentsReceived_;
+    /// Bytes received so far
+    unsigned bytesReceived_;
+    /// Timer for current batch of fragments
+    Timer batchTimer_;
+    /// Timer for the whole transfer
+    Timer receiveTimer_;
+    /// First fragment number in the current batch
+    unsigned batchStart_;
+    /// Number of fragments in the current batch
+    unsigned batchSize_;
+    /// Number of fragments in the previous batch
+    unsigned lastBatchSize_;
+    /// Download rate of the previous batch
+    float lastDataRate_;
+};
+
+/// Multiplayer client subsystem
+class Client : public Object
+{
+    OBJECT(Object);
+    
+public:
+    /// Construct
+    Client(Context* context);
+    /// Destruct
+    virtual ~Client();
+    
+    /// Set scene to use. Will be cleared of any existing content
+    void SetScene(Scene* scene);
+    /// Set package download directory
+    void SetDownloadDirectory(const std::string& path);
+    /// Connect to a server
+    bool Connect(const std::string& address, unsigned short port, const VariantMap& loginData = VariantMap());
+    /// Disconnect from a server
+    void Disconnect();
+    /// Set client controls to be sent over the network
+    void SetControls(const Controls& controls);
+    /// Set client position to be sent over the network for node relevancy calculations
+    void SetPosition(const Vector3& position);
+    /// Send and receive packets and update the scene
+    void Update(float timeStep);
+    
+    /// Return the scene
+    Scene* GetScene() const { return scene_; }
+    /// Return the server connection
+    Connection* GetServerConnection() const { return serverConnection_; }
+    /// Return whether connected
+    bool IsConnected() const;
+    /// Return whether scene join pending
+    bool IsJoinPending() const;
+    /// Return whether joined a scene
+    bool IsJoined() const;
+    /// Return network updates per second
+    int GetNetFps() const { return netFps_; }
+    /// Return client frame number
+    unsigned short GetFrameNumber() const { return frameNumber_; }
+    /// Return client controls
+    const Controls& GetControls() const;
+    /// Return client position
+    const Vector3& GetPosition() const;
+    /// Return scene info
+    const SceneInfo& GetSceneInfo() const { return sceneInfo_; }
+    /// Return number of ongoing file transfers
+    unsigned GetNumFileTransfers() { return fileTransfers_.size(); }
+    /// Return ongoing file transfers
+    const std::map<StringHash, FileTransfer>& GetFileTransfers() { return fileTransfers_; }
+    /// Return download directory
+    const std::string& GetDownloadDirectory() const { return downloadDirectory_; }
+    /// Return file transfer status as text
+    std::string GetFileTransferStatus() const;
+    
+private:
+    /// Handle network peer disconnect event
+    void HandlePeerDisconnected(StringHash eventType, VariantMap& eventData);
+    /// Handle file transfer complete event
+    void HandleFileTransferCompleted(StringHash eventType, VariantMap& eventData);
+    /// Handle file transfer failed event
+    void HandleFileTransferFailed(StringHash eventType, VariantMap& eventData);
+    /// Handle async scene loading finished event
+    void HandleAsyncLoadFinished(StringHash eventType, VariantMap& eventData);
+    /// Handle a reliable network packet
+    void HandleReliablePacket(VectorBuffer& packet);
+    /// Handle a challenge packet
+    void HandleChallenge(VectorBuffer& packet);
+    /// Handle a scene info packet
+    void HandleSceneInfo(VectorBuffer& packet);
+    /// Handle a file transfer packet
+    void HandleTransferData(VectorBuffer& packet);
+    /// Handle a file transfer failed packet
+    void HandleTransferFailed(VectorBuffer& packet);
+    /// Handle a join reply packet
+    void HandleJoinReply(VectorBuffer& packet);
+    /// Handle a full scene update from the server
+    void HandleFullUpdate(VectorBuffer& packet);
+    /// Handle a server update packet
+    void HandleServerUpdate(VectorBuffer& packet, bool initial = false);
+    /// Check whether packages need to be downloaded to join the scene
+    unsigned CheckPackages();
+    /// Begin a file download
+    bool RequestFile(const std::string& fileName, unsigned size, unsigned checksum);
+    /// Begin setup of the client scene
+    void SetupScene();
+    /// Send join scene packet after scene setup is complete
+    void SendJoinScene();
+    /// Send join failed event
+    void JoinFailed(const std::string& reason);
+    /// Send a client update packet
+    void SendClientUpdate();
+    /// Read a server update block
+    void ReadNetUpdateBlock(Deserializer& source, unsigned char msgID, std::set<unsigned>& updatedNodes, std::set<unsigned>& updatedComponents);
+    
+    /// Scene
+    SharedPtr<Scene> scene_;
+    /// Server connection
+    SharedPtr<Connection> serverConnection_;
+    /// Network updates per second
+    int netFps_;
+    /// Network update time accumulator
+    float timeAcc_;
+    /// Client framenumber
+    unsigned short frameNumber_;
+    /// Scene info
+    SceneInfo sceneInfo_;
+    /// Ongoing file downloads
+    std::map<StringHash, FileTransfer> fileTransfers_;
+    /// Directory to use for downloads
+    std::string downloadDirectory_;
+    /// Downloads required to join the scene
+    std::set<std::string> pendingDownloads_;
+    /// Login data to send to the server
+    VariantMap pendingLoginData_;
+};

+ 285 - 0
Engine/Network/Connection.cpp

@@ -0,0 +1,285 @@
+//
+// Urho3D Engine
+// Copyright (c) 2008-2011 Lasse Öörni
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#include "Precompiled.h"
+#include "Connection.h"
+#include "Log.h"
+#include "Protocol.h"
+#include "ProtocolEvents.h"
+#include "Scene.h"
+#include "StringUtils.h"
+
+#include "DebugNew.h"
+
+static const float RTT_LERP_FACTOR = 0.1f;
+
+OBJECTTYPESTATIC(Connection);
+
+Connection::Connection(Context* context, Peer* peer) :
+    Object(context),
+    peer_(peer),
+    frameNumber_(0),
+    frameAck_(0),
+    eventFrameNumber_(0),
+    roundTripTime_(0.0f),
+    challenge_(0),
+    hasChallenge_(false),
+    joinState_(JS_NOTINSCENE),
+    position_(Vector3::ZERO)
+{
+}
+
+Connection::~Connection()
+{
+    // Make sure any references in the scene are cleared
+    LeftScene();
+}
+
+void Connection::SendReliable(const VectorBuffer& packet)
+{
+    Send(packet, true);
+}
+
+void Connection::SendUnreliable(const VectorBuffer& packet)
+{
+    Send(packet, false);
+}
+
+bool Connection::ReceiveReliable(VectorBuffer& packet)
+{
+    return Receive(packet, true);
+}
+
+bool Connection::ReceiveUnreliable(VectorBuffer& packet)
+{
+    return Receive(packet, false);
+}
+
+void Connection::Disconnect()
+{
+    LeftScene();
+    peer_->Disconnect();
+}
+
+void Connection::forceDisconnect()
+{
+    LeftScene();
+    peer_->ForceDisconnect();
+}
+
+void Connection::SetLoginData(const VariantMap& loginData)
+{
+    loginData_ = loginData;
+}
+
+void Connection::SetScene(Scene* scene)
+{
+    // Leave previous scene first
+    if ((scene_) && (scene_ != scene))
+        LeftScene();
+    
+    scene_ = scene;
+    joinState_ = JS_PREPARESCENE;
+    
+    // Client: set the proxy flag to the scene
+    if (peer_->GetPeerType() == PEER_SERVER)
+        scene_->SetNetworkMode(NM_CLIENT);
+}
+
+void Connection::JoinedScene()
+{
+    if (!scene_)
+        return;
+    
+    if (scene_->GetNetworkMode() == NM_SERVER)
+    {
+        // Server: send event, so that game-specific code may create the player node/nodes
+        using namespace ClientJoinedScene;
+        
+        VariantMap eventData;
+        eventData[P_CONNECTION] = (void*)this;
+        eventData[P_SCENE] = (void*)scene_.GetPtr();
+        SendEvent(E_CLIENTJOINEDSCENE, eventData);
+        
+        LOGINFO("Client " + GetIdentity() + " joined scene " + scene_->GetName());
+    }
+    
+    joinState_ = JS_SENDFULLUPDATE;
+}
+
+void Connection::LeftScene()
+{
+    if (scene_)
+    {
+        NetworkMode mode = scene_->GetNetworkMode();
+        
+        if (mode == NM_SERVER)
+        {
+            // Server: send event so that game-specific code may remove the player node/nodes
+            using namespace ClientLeftScene;
+            
+            VariantMap eventData;
+            eventData[P_CONNECTION] = (void*)this;
+            eventData[P_SCENE] = (void*)scene_.GetPtr();
+            SendEvent(E_CLIENTLEFTSCENE, eventData);
+            
+            LOGINFO("Client " + GetIdentity() + " left scene " + scene_->GetName());
+        }
+        
+        // Remove owner reference from any nodes
+        scene_->ResetOwner(this);
+        
+        // Reset the client mode if set (scene can now be reused as singleplayer again)
+        if (mode == NM_CLIENT)
+        {
+            scene_->Clear();
+            scene_->SetNetworkMode(NM_NONETWORK);
+        }
+    }
+    
+    scene_.Reset();
+    remoteEvents_.clear();
+    sceneState_.Clear();
+    joinState_ = JS_NOTINSCENE;
+}
+
+void Connection::SetChallenge(unsigned challenge)
+{
+    challenge_ = challenge;
+    hasChallenge_ = true;
+}
+
+void Connection::SetJoinState(JoinState state)
+{
+    joinState_ = state;
+}
+
+void Connection::SetFrameNumbers(unsigned short frameNumber, unsigned short frameAck)
+{
+    frameNumber_ = frameNumber;
+    frameAck_ = frameAck;
+}
+
+void Connection::UpdateRoundTripTime(int netFps, unsigned short frameNumber)
+{
+    unsigned short frameDiff = frameNumber - frameAck_;
+    if (frameDiff >= 0x8000)
+        frameDiff = frameAck_ - frameNumber;
+    
+    float newRtt = (1.0f / netFps) * frameDiff;
+    roundTripTime_ = Lerp(roundTripTime_, newRtt, RTT_LERP_FACTOR);
+}
+
+void Connection::SetControls(const Controls& controls)
+{
+    controls_ = controls;
+}
+
+void Connection::SetPosition(const Vector3& position)
+{
+    position_ = position;
+}
+
+void Connection::addRemoteEvent(const RemoteEvent& remoteEvent)
+{
+    remoteEvents_.push_back(remoteEvent);
+}
+
+bool Connection::CheckRemoteEventFrame(const RemoteEvent& remoteEvent, unsigned short previousEventFrameNumber)
+{
+    // Check against previous event framenumber, and update current
+    if (!CheckFrameNumber(remoteEvent.frameNumber_, previousEventFrameNumber, false))
+        return false;
+    if (CheckFrameNumber(remoteEvent.frameNumber_, eventFrameNumber_))
+        eventFrameNumber_ = remoteEvent.frameNumber_;
+    return true;
+}
+
+void Connection::PurgeAckedSceneState()
+{
+    sceneState_.Acked(frameAck_);
+}
+
+void Connection::PurgeAckedRemoteEvents(unsigned short frameNumber)
+{
+    for (std::vector<RemoteEvent>::iterator i = remoteEvents_.begin(); i != remoteEvents_.end();)
+    {
+        bool erase = false;
+        
+        if (!CheckFrameNumber(i->frameNumber_, frameAck_, false))
+            erase = true;
+        else if (i->timeToLive_)
+        {
+            unsigned short expiryFrameNumber = i->frameNumber_ + i->timeToLive_;
+            if (!expiryFrameNumber)
+                ++expiryFrameNumber;
+            if (!CheckFrameNumber(expiryFrameNumber, frameNumber))
+                erase = true;
+        }
+        
+        if (erase)
+            i = remoteEvents_.erase(i);
+        else
+            ++i;
+    }
+}
+
+void Connection::ClearSceneState()
+{
+    sceneState_.Clear();
+}
+
+void Connection::ClearRemoteEvents()
+{
+    remoteEvents_.clear();
+}
+
+std::string Connection::GetIdentity() const
+{
+    if (peer_)
+        return peer_->GetAddress() + ":" + ToString(peer_->GetPort());
+    else
+        return "Unknown";
+}
+
+bool Connection::IsConnected() const
+{
+    return peer_->GetConnectionState() == CS_CONNECTED;
+}
+
+void Connection::Send(const VectorBuffer& packet, bool reliable)
+{
+    if ((packet.GetSize()) && (peer_->GetConnectionState() == CS_CONNECTED))
+        peer_->Send(packet, reliable ? CHN_RELIABLE : CHN_UNRELIABLE, reliable);
+}
+
+bool Connection::Receive(VectorBuffer& packet, bool reliable)
+{
+    if (peer_->GetConnectionState() == CS_CONNECTED)
+        return peer_->Receive(packet, reliable ? CHN_RELIABLE : CHN_UNRELIABLE);
+    else
+    {
+        peer_->FlushPackets();
+        return false;
+    }
+}

+ 178 - 0
Engine/Network/Connection.h

@@ -0,0 +1,178 @@
+//
+// Urho3D Engine
+// Copyright (c) 2008-2011 Lasse Öörni
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+#include "Controls.h"
+#include "Peer.h"
+#include "RemoteEvent.h"
+#include "ReplicationState.h"
+#include "Timer.h"
+
+class Scene;
+
+static const int KEY_LENGTH = 256;
+
+/// Client's scene join state
+enum JoinState
+{
+    JS_NOTINSCENE = 0,
+    JS_PREPARESCENE,
+    JS_LOADSCENE,
+    JS_SENDFULLUPDATE,
+    JS_WAITFORACK,
+    JS_SENDDELTAS
+};
+
+/// Multiplayer protocol connection
+class Connection : public Object
+{
+    OBJECT(Connection);
+    
+public:
+    /// Construct
+    Connection(Context* context, Peer* peer);
+    /// Destruct
+    virtual ~Connection();
+    
+    /// Send a packet on the reliable channel
+    void SendReliable(const VectorBuffer& packet);
+    /// Send a packet on the unreliable channel
+    void SendUnreliable(const VectorBuffer& packet);
+    /// Attempt to receive a packet on the reliable channel. Return true if a packet was received
+    bool ReceiveReliable(VectorBuffer& packet);
+    /// Attempt to receive a packet on the unreliable channel. Return true if a packet was received
+    bool ReceiveUnreliable(VectorBuffer& packet);
+    /// Disconnect
+    void Disconnect();
+    /// Disconnect immediately
+    void forceDisconnect();
+    /// Set scene
+    void SetScene(Scene* scene);
+    /// Joined scene
+    void JoinedScene();
+    /// Left scene
+    void LeftScene();
+    /// Set login data
+    void SetLoginData(const VariantMap& loginData);
+    /// Set server challenge value
+    void SetChallenge(unsigned challenge);
+    /// Set client's join state
+    void SetJoinState(JoinState state);
+    /// Set remote frame number and last acked local frame number
+    void SetFrameNumbers(unsigned short frameNumber, unsigned short frameAck);
+    /// Set client controls
+    void SetControls(const Controls& controls);
+    /// Set client position for node relevancy calculations
+    void SetPosition(const Vector3& position);
+    /// Queue a remote event to be sent
+    void addRemoteEvent(const RemoteEvent& remoteEvent);
+    /// Check if a received remote event should be dispatched, or if it was dispatched already
+    bool CheckRemoteEventFrame(const RemoteEvent& remoteEvent, unsigned short previousEventFrameNumber);
+    /// Add unacked controls for possible client-side prediction / replay
+    void AddUnackedControls(unsigned short frameNumber, const Controls& controls);
+    /// Remove scene revisions the client has already received
+    void PurgeAckedSceneState();
+    //! Remove controls the server has already received
+    void PurgeAckedControls();
+    /// Remove events the remote end has already received
+    void PurgeAckedRemoteEvents(unsigned short frameNumber);
+    /// Remove all scene revision data
+    void ClearSceneState();
+    /// Remove all remote events
+    void ClearRemoteEvents();
+    /// Update the round trip time using a smoothed average
+    void UpdateRoundTripTime(int netFps, unsigned short frameNumber);
+    
+    /// Return network peer
+    Peer* GetPeer() const { return peer_; }
+    /// Return scene the client has joined, if any
+    Scene* GetScene() const { return scene_; }
+    /// Return identity string (address:port)
+    std::string GetIdentity() const;
+    /// Return login data
+    const VariantMap& GetLoginData() const { return loginData_; }
+    /// Return challenge value
+    unsigned GetChallenge() const { return challenge_; }
+    /// Return whether connected
+    bool IsConnected() const;
+    /// Return whether a challenge has been assigned
+    bool HasChallenge() const { return hasChallenge_; }
+    /// Return the client's scene join state
+    JoinState GetJoinState() const { return joinState_; }
+    /// Return the remote frame number
+    unsigned short GetFrameNumber() const { return frameNumber_; }
+    /// Return the last acked local frame number
+    unsigned short GetFrameAck() const { return frameNumber_; }
+    /// Return the last processed remote event frame number
+    unsigned short GetEventFrameNumber() const { return eventFrameNumber_; }
+    /// Return the current estimated round trip time
+    float GetRoundTripTime() const { return roundTripTime_; }
+    /// Return client controls
+    const Controls& GetControls() { return controls_; }
+    /// Return client position
+    const Vector3& GetPosition() { return position_; }
+    /// Return unacked remote events
+    std::vector<RemoteEvent>& GetUnackedRemoteEvents() { return remoteEvents_; }
+    /// Return unacked controls
+    std::vector<std::pair<unsigned short, Controls> >& GetUnackedControls() { return unackedControls_; }
+    /// Return unacked scene revisions
+    SceneReplicationState& GetSceneState() { return sceneState_; }
+    
+private:
+    /// Send a packet
+    void Send(const VectorBuffer& packet, bool reliable);
+    /// Return a packet
+    bool Receive(VectorBuffer& packet, bool reliable);
+    
+    /// Network peer
+    SharedPtr<Peer> peer_;
+    /// Scene
+    SharedPtr<Scene> scene_;
+    /// Login data
+    VariantMap loginData_;
+    /// Challenge value
+    unsigned challenge_;
+    /// Challenge assigned flag
+    bool hasChallenge_;
+    /// Remote frame number
+    unsigned short frameNumber_;
+    /// Last acked local frame number
+    unsigned short frameAck_;
+    /// Last processed remote event frame number
+    unsigned short eventFrameNumber_;
+    /// Current estimated round trip time
+    float roundTripTime_;
+    /// Client's scene join state
+    JoinState joinState_;
+    /// Client controls
+    Controls controls_;
+    /// Client position
+    Vector3 position_;
+    /// Remote events currently being sent
+    std::vector<RemoteEvent> remoteEvents_;
+    //! Unacked controls
+    std::vector<std::pair<unsigned short, Controls> > unackedControls_;
+    /// Unacked scene revisions
+    SceneReplicationState sceneState_;
+};

+ 88 - 0
Engine/Network/Protocol.h

@@ -0,0 +1,88 @@
+//
+// Urho3D Engine
+// Copyright (c) 2008-2011 Lasse Öörni
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+#include "Deserializer.h"
+#include "Serializer.h"
+
+// Channel numbers
+static const unsigned char CHN_RELIABLE = 0;
+static const unsigned char CHN_UNRELIABLE = 1;
+
+// File transfer fragment size
+static const unsigned FILE_FRAGMENT_SIZE = 1024;
+
+// Reliable messages (chn.0), server to client
+static const unsigned char MSG_CHALLENGE = 0x00;
+static const unsigned char MSG_SCENEINFO = 0x01;
+static const unsigned char MSG_TRANSFERDATA = 0x02;
+static const unsigned char MSG_TRANSFERFAILED = 0x03;
+static const unsigned char MSG_JOINREPLY = 0x04;
+static const unsigned char MSG_FULLUPDATE = 0x05;
+
+// Reliable messages (chn.0), client to server
+static const unsigned char MSG_LOGIN = 0x06;
+static const unsigned char MSG_REQUESTFILE = 0x07;
+static const unsigned char MSG_JOINSCENE = 0x08;
+static const unsigned char MSG_FULLUPDATEACK = 0x09;
+
+// Server/clientupdate sub-messages. Unreliable chn.1 messages consist entirely of these
+static const unsigned char MSG_CREATENODE = 0x0a;
+static const unsigned char MSG_UPDATENODE = 0x0b;
+static const unsigned char MSG_REMOVENODE = 0x0c;
+static const unsigned char MSG_CREATECOMPONENT = 0x0d;
+static const unsigned char MSG_UPDATECOMPONENT = 0x0e;
+static const unsigned char MSG_REMOVECOMPONENT = 0x0f;
+static const unsigned char MSG_CONTROLS = 0x10;
+static const unsigned char MSG_REMOTEEVENT = 0x11;
+static const unsigned char MSG_REMOTENODEEVENT = 0x12;
+
+/// Write a 24-bit networked Node or Component ID
+inline void WriteNetID(Serializer& dest, unsigned id)
+{
+    // Write the low 24 bits
+    dest.Write(&id, 3);
+}
+
+// Read a 24-bit networked Node or Component ID
+inline unsigned ReadNetID(Deserializer& source)
+{
+    unsigned ret = 0;
+    // Read the low 24 bits
+    source.Read(&ret, 3);
+    return ret;
+}
+
+/// Compare network frame numbers, taking wrapping into account
+inline bool CheckFrameNumber(unsigned short lhs, unsigned short rhs, bool sameFrameOk = true)
+{
+    // Frame number 0 means "frame never received", so in that case lhs is always considered "newer"
+    if ((lhs) && (!rhs))
+        return true;
+    
+    if ((!sameFrameOk) && (lhs == rhs))
+        return false;
+    
+    return ((lhs - rhs) & 0xffff) < 0x8000;
+}

+ 102 - 0
Engine/Network/ProtocolEvents.h

@@ -0,0 +1,102 @@
+//
+// Urho3D Engine
+// Copyright (c) 2008-2011 Lasse Öörni
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+#include "Event.h"
+
+/// (Server) Client has sent login
+EVENT(E_CLIENTLOGIN, ClientLogin)
+{
+    PARAM(P_CONNECTION, Connection);      // Connection pointer
+    PARAM(P_AUTHORIZE, Authorize);        // bool (set false to deny access)
+    // Extra login data may exist if sent by the client
+}
+
+/// (Server) Client has joined a scene
+EVENT(E_CLIENTJOINEDSCENE, ClientJoinedScene)
+{
+    PARAM(P_CONNECTION, Connection);      // Connection pointer
+    PARAM(P_SCENE, Scene);                // Scene pointer
+}
+
+/// (Server) Client has left a scene
+EVENT(E_CLIENTLEFTSCENE, ClientLeftScene)
+{
+    PARAM(P_CONNECTION, Connection);      // Connection pointer
+    PARAM(P_SCENE, Scene);                // Scene pointer
+}
+
+/// (Server) Controls have been received from the client
+EVENT(E_CLIENTCONTROLS, ClientControls)
+{
+    PARAM(P_CONNECTION, Connection);      // Connection pointer
+    PARAM(P_FRAMENUMBER, FrameNumber);    // int (1-65535)
+    PARAM(P_BUTTONS, Buttons);            // int
+    PARAM(P_YAW, Yaw);                    // float (degrees)
+    PARAM(P_PITCH, Pitch);                // float (degrees)
+    // Other parameters may exist as part of the user extra controls variant map
+}
+
+/// (Server) Client has been disconnected
+EVENT(E_CLIENTDISCONNECTED, ClientDisconnected)
+{
+    PARAM(P_CONNECTION, Connection);      // Connection pointer
+}
+
+/// (Client) Joined the scene
+EVENT(E_JOINEDSCENE, JoinedScene)
+{
+}
+
+/// (Client) Failed to join the scene
+EVENT(E_JOINSCENEFAILED, JoinSceneFailed)
+{
+    PARAM(P_REASON, Reason);              // string
+}
+
+/// (Client) File transfer completed
+EVENT(E_FILETRANSFERCOMPLETED, FileTransferCompleted)
+{
+    PARAM(P_FILENAME, FileName);          // string
+    PARAM(P_FULLPATH, FullPath);          // string
+}
+
+/// (Client) File transfer failed
+EVENT(E_FILETRANSFERFAILED, FileTransferFailed)
+{
+    PARAM(P_FILENAME, FileName);          // string
+    PARAM(P_REASON, FileName);            // string
+}
+
+/// (Client) Controls update request, sent just before client update packet
+EVENT(E_CONTROLSUPDATE, ControlsUpdate)
+{
+    PARAM(P_SCENE, Scene);                // Scene pointer
+}
+
+/// (Client) Server update received
+EVENT(E_SERVERUPDATE, ServerUpdate)
+{
+    PARAM(P_SCENE, Scene);                // Scene pointer
+}

+ 72 - 0
Engine/Network/RemoteEvent.cpp

@@ -0,0 +1,72 @@
+//
+// Urho3D Engine
+// Copyright (c) 2008-2011 Lasse Öörni
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#include "Precompiled.h"
+#include "Connection.h"
+#include "Log.h"
+#include "Protocol.h"
+#include "Scene.h"
+#include "StringUtils.h"
+
+#include <set>
+
+#include "DebugNew.h"
+
+void RemoteEvent::Write(Serializer& dest) const
+{
+    dest.WriteUShort(frameNumber_);
+    if (nodeID_)
+        WriteNetID(dest, nodeID_);
+    dest.WriteStringHash(eventType_);
+    dest.WriteVariantMap(eventData_);
+}
+
+void RemoteEvent::Read(Deserializer& source, bool hasEntity)
+{
+    frameNumber_ = source.ReadUShort();
+    if (hasEntity)
+        nodeID_ = ReadNetID(source);
+    else
+        nodeID_ = 0;
+    eventType_ = source.ReadStringHash();
+    eventData_ = source.ReadVariantMap();
+}
+
+bool RemoteEvent::Dispatch(Connection* sender, Scene* scene)
+{
+    if ((!sender) || (!scene))
+        return false;
+    
+    /// \todo Add fixed blacklist check for event type, so that it is not an internal engine event
+    if (!nodeID_)
+        sender->SendEvent(eventType_, eventData_);
+    else
+    {
+        Node* node = scene->GetNodeByID(nodeID_);
+        if (!node)
+            return false;
+        else
+            sender->SendEvent(node, eventType_, eventData_);
+    }
+    return true;
+}

+ 83 - 0
Engine/Network/RemoteEvent.h

@@ -0,0 +1,83 @@
+//
+// Urho3D Engine
+// Copyright (c) 2008-2011 Lasse Öörni
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#ifndef SCENE_REMOTEEVENT_H
+#define SCENE_REMOTEEVENT_H
+
+#include "Object.h"
+
+class Connection;
+class Deserializer;
+class Scene;
+class Serializer;
+
+//! An event to be sent over the network
+struct RemoteEvent
+{
+    //! Construct as undefined
+    RemoteEvent()
+    {
+    }
+    
+    //! Construct with parameters
+    RemoteEvent(StringHash eventType, const VariantMap& eventData, Connection* receiver, unsigned short timeToLive) :
+        nodeID_(0),
+        eventType_(eventType),
+        eventData_(eventData),
+        receiver_(receiver),
+        timeToLive_(timeToLive)
+    {
+    }
+    
+    //! Construct with target entity and parameters
+    RemoteEvent(unsigned nodeID, StringHash eventType, const VariantMap& eventData, Connection* receiver, unsigned short timeToLive) :
+        nodeID_(nodeID),
+        eventType_(eventType),
+        eventData_(eventData),
+        receiver_(receiver),
+        timeToLive_(timeToLive)
+    {
+    }
+    
+    //! Write to a stream
+    void Write(Serializer& dest) const;
+    //! Read from a stream
+    void Read(Deserializer& source, bool hasEntity);
+    //! Dispatch at the remote end
+    bool Dispatch(Connection* sender, Scene* scene);
+    
+    //! Frame number
+    unsigned short frameNumber_;
+    //! Event type
+    StringHash eventType_;
+    //! Event parameters
+    VariantMap eventData_;
+    //! Target entity ID
+    unsigned nodeID_;
+    //! Receiving connection. If null on the server, broadcast to all. On client has no significance
+    Connection* receiver_;
+    //! Time to live in network frames. If not acked when expires, will no longer be sent. 0 is infinite
+    unsigned short timeToLive_;
+};
+
+#endif // SCENE_REMOTEEVENT_H

+ 231 - 0
Engine/Network/ReplicationState.cpp

@@ -0,0 +1,231 @@
+//
+// Urho3D Engine
+// Copyright (c) 2008-2011 Lasse Öörni
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#include "Precompiled.h"
+#include "Protocol.h"
+#include "ReplicationState.h"
+
+#include <cstring>
+
+#include "DebugNew.h"
+
+VectorBuffer RevisionBuffer::emptyBaseRevision;
+
+void RevisionBuffer::PurgeOld(unsigned short frameNumber)
+{
+    // Remove revisions which are older than last ack, but make sure at least 1 revision remains
+    if (!revisions_.size())
+        return;
+    
+    unsigned eraseCount = 0;
+    for (std::vector<Revision>::iterator i = revisions_.begin(); (i != revisions_.end() - 1) &&
+        (revisions_.size() - eraseCount > 1); ++i)
+    {
+        // If oldest and second-oldest are both older than framenumber, or if the second-oldest is on the exact same frame,
+        // can delete the oldest
+        std::vector<Revision>::iterator j = i + 1;
+        if ((j->frameNumber_ == frameNumber) || ((!CheckFrameNumber(i->frameNumber_, frameNumber)) &&
+            (!CheckFrameNumber(j->frameNumber_, frameNumber))))
+            eraseCount++;
+        else
+            break;
+    }
+    
+    if (eraseCount)
+    {
+        revisions_.erase(revisions_.begin(), revisions_.begin() + eraseCount);
+        
+        // Move the data and datapointers
+        unsigned delta = revisions_[0].offset_;
+        void* src = GetModifiableData() + delta;
+        void* dest = GetModifiableData();
+        memmove(dest, src, GetSize() - delta);
+        Resize(GetSize() - delta);
+        
+        for (std::vector<Revision>::iterator i = revisions_.begin(); i != revisions_.end(); ++i)
+            i->offset_ -= delta;
+    }
+}
+
+bool RevisionBuffer::HasUnAcked(unsigned short frameNumber) const
+{
+    for (std::vector<Revision>::const_iterator i = revisions_.begin(); i != revisions_.end(); ++i)
+    {
+        if (CheckFrameNumber(i->frameNumber_, frameNumber, false))
+            return true;
+    }
+    
+    return false;
+}
+
+ComponentReplicationState::ComponentReplicationState() :
+    exists_(true),
+    createdFrame_(0),
+    removedFrame_(0)
+{
+}
+
+void ComponentReplicationState::Created(unsigned short frameNumber)
+{
+    exists_ = true;
+    createdFrame_ = frameNumber;
+    removedFrame_ = 0;
+}
+
+void ComponentReplicationState::Removed(unsigned short frameNumber)
+{
+    exists_ = false;
+    createdFrame_ = 0;
+    removedFrame_ = frameNumber;
+    
+    // Clear revisions in case this component gets recreated
+    revisions_.Clear();
+}
+
+void ComponentReplicationState::Acked(unsigned short lastAck)
+{
+    // If ack is newer or same age than the creation or removal event, reset it
+    if ((createdFrame_) && (CheckFrameNumber(lastAck, createdFrame_)))
+        createdFrame_ = 0;
+    if ((removedFrame_) && (CheckFrameNumber(lastAck, removedFrame_)))
+        removedFrame_ = 0;
+    
+    // Remove old data revisions
+    revisions_.PurgeOld(lastAck);
+}
+
+bool ComponentReplicationState::CanRemove() const
+{
+    // Can be removed from the replication state if no longer exists, and the remove has been acked
+    return (!exists_) && (!removedFrame_);
+}
+
+NodeReplicationState::NodeReplicationState() :
+    exists_(true),
+    createdFrame_(0),
+    removedFrame_(0),
+    stayRelevantTime_(0)
+{
+}
+
+void NodeReplicationState::Created(unsigned short frameNumber)
+{
+    exists_ = true;
+    createdFrame_ = frameNumber;
+    removedFrame_ = 0;
+}
+
+void NodeReplicationState::Removed(unsigned short frameNumber)
+{
+    exists_ = false;
+    createdFrame_ = 0;
+    removedFrame_ = frameNumber;
+    
+    // Clear property revisions & components in case this node gets recreated
+    revisions_.Clear();
+    components_.clear();
+}
+
+void NodeReplicationState::Acked(unsigned short lastAck)
+{
+    // If ack is newer or same age than the creation or removal event, reset it
+    if ((createdFrame_) && (CheckFrameNumber(lastAck, createdFrame_)))
+        createdFrame_ = 0;
+    if ((removedFrame_) && (CheckFrameNumber(lastAck, removedFrame_)))
+        removedFrame_ = 0;
+    
+    // Remove old property revisions
+    revisions_.PurgeOld(lastAck);
+    
+    // Ack each component and remove if necessary
+    for (std::map<ShortStringHash, ComponentReplicationState>::iterator j = components_.begin(); j != components_.end();)
+    {
+        std::map<ShortStringHash, ComponentReplicationState>::iterator component = j;
+        ++j;
+        
+        component->second.Acked(lastAck);
+        if (component->second.CanRemove())
+            components_.erase(component);
+    }
+}
+
+bool NodeReplicationState::HasUnAcked(unsigned short frameNumber) const
+{
+    if (revisions_.HasUnAcked(frameNumber))
+        return true;
+    
+    for (std::map<ShortStringHash, ComponentReplicationState>::const_iterator i = components_.begin(); i != components_.end(); ++i)
+    {
+        if (i->second.revisions_.HasUnAcked(frameNumber))
+            return true;
+    }
+    
+    return false;
+}
+
+bool NodeReplicationState::CanRemove() const
+{
+    // Can be removed from the replication state if no longer exists, and the remove has been acked
+    return (!exists_) && (!removedFrame_);
+}
+
+unsigned NodeReplicationState::GetRevisionCount() const
+{
+    // Return the highest of property revisions and component revisions
+    int maxRevisions = revisions_.GetCount();
+    
+    for (std::map<ShortStringHash, ComponentReplicationState>::const_iterator i = components_.begin(); i != components_.end(); ++i)
+        maxRevisions = Max((int)maxRevisions, (int)i->second.GetRevisionCount());
+    
+    return maxRevisions;
+}
+
+void SceneReplicationState::Clear()
+{
+    nodes_.clear();
+}
+
+void SceneReplicationState::Acked(unsigned short lastAck)
+{
+    // Ack each node and remove if necessary
+    for (std::map<unsigned, NodeReplicationState>::iterator i = nodes_.begin(); i != nodes_.end();)
+    {
+        std::map<unsigned, NodeReplicationState>::iterator node = i;
+        ++i;
+        
+        node->second.Acked(lastAck);
+        if (node->second.CanRemove())
+            nodes_.erase(node);
+    }
+}
+
+unsigned SceneReplicationState::GetRevisionCount() const
+{
+    // Get the highest revision count of all nodes;
+    int maxRevisions = 0;
+    
+    for (std::map<unsigned, NodeReplicationState>::const_iterator i = nodes_.begin(); i != nodes_.end(); ++i)
+        maxRevisions = Max((int)maxRevisions, (int)i->second.GetRevisionCount());
+    
+    return maxRevisions;
+}

+ 196 - 0
Engine/Network/ReplicationState.h

@@ -0,0 +1,196 @@
+//
+// Urho3D Engine
+// Copyright (c) 2008-2011 Lasse Öörni
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+#include "Node.h"
+#include "VectorBuffer.h"
+
+#include <list>
+
+/// A revision in the buffer
+struct Revision
+{
+    /// Frame number stored on
+    unsigned short frameNumber_;
+    /// Offset in buffer
+    unsigned offset_;
+};
+
+/// A buffer that can store several data revisions
+class RevisionBuffer : public VectorBuffer
+{
+public:
+    /// Return oldest revision
+    Deserializer& GetBase()
+    {
+        if (revisions_.empty())
+            return emptyBaseRevision;
+        else
+        {
+            Seek(revisions_[0].offset_);
+            return *this;
+        }
+    }
+    
+    /// Commit a revision
+    void Commit(unsigned short frameNumber, VectorBuffer& data)
+    {
+        // Do not make zero-length commits
+        if (!data.GetPosition())
+            return;
+        
+        Revision newRevision;
+        newRevision.frameNumber_ = frameNumber;
+        newRevision.offset_ = GetSize();
+        Seek(newRevision.offset_);
+        
+        // Note: write only up to source's current position, so that can use Seek() instead of resize() to write multiple commits
+        // to the same buffer
+        Write(data.GetData(), data.GetPosition());
+        revisions_.push_back(newRevision);
+    }
+    
+    /// Clear revisions
+    void Clear()
+    {
+        VectorBuffer::Clear();
+        revisions_.clear();
+    }
+    
+    /// Purge revisions that are older than frame
+    void PurgeOld(unsigned short frameNumber);
+    
+    /// Return whether has unacked revisions newer than frame
+    bool HasUnAcked(unsigned short frameNumber) const;
+    /// Return number of revisions
+    unsigned GetCount() const { return revisions_.size(); }
+    
+private:
+    /// Revision information
+    std::vector<Revision> revisions_;
+    
+    /// Empty revision
+    static VectorBuffer emptyBaseRevision;
+};
+
+/// Per-client component state for delta updates
+class ComponentReplicationState
+{
+public:
+    /// Construct
+    ComponentReplicationState();
+    
+    /// Mark that component was created
+    void Created(unsigned short frameNumber);
+    /// Mark that component was removed
+    void Removed(unsigned short frameNumber);
+    /// Mark that client acked state up to a specific frame
+    void Acked(unsigned short lastAck);
+    /// Return whether exists currently
+    bool Exists() const { return exists_; }
+    /// Return whether has no unsent changes and can be removed
+    bool CanRemove() const;
+    /// Return number of revisions
+    unsigned GetRevisionCount() const { return revisions_.GetCount(); }
+    
+    /// Exists flag
+    bool exists_;
+    /// Frame number was created on
+    unsigned short createdFrame_;
+    /// Frame number was removed on
+    unsigned short removedFrame_;
+    /// Stored state revisions
+    RevisionBuffer revisions_;
+};
+
+/// Per-client node state for delta updates
+class NodeReplicationState
+{
+public:
+    /// Construct
+    NodeReplicationState();
+    
+    /// Return per-client state for a component, or null if not found
+    ComponentReplicationState* FindComponent(ShortStringHash combinedHash)
+    {
+        std::map<ShortStringHash, ComponentReplicationState>::iterator i = components_.find(combinedHash);
+        if (i != components_.end())
+            return &i->second;
+        else
+            return 0;
+    }
+    
+    /// Mark that node was created
+    void Created(unsigned short frameNumber);
+    /// Mark that node was removed
+    void Removed(unsigned short frameNumber);
+    /// Mark that client acked state up to a specific frame
+    void Acked(unsigned short lastAck);
+    /// Return whether node exists currently
+    bool Exists() const { return exists_; }
+    /// Return whether has unacked state newer than frame
+    bool HasUnAcked(unsigned short frameNumber) const;
+    /// Return whether has no unsent changes and can be removed
+    bool CanRemove() const;
+    /// Return highest number of state revisions
+    unsigned GetRevisionCount() const;
+    
+    /// Exists flag
+    bool exists_;
+    /// Frame number was created on
+    unsigned short createdFrame_;
+    /// Frame number was removed on
+    unsigned short removedFrame_;
+    /// Relevancy time counter in server frames
+    int stayRelevantTime_;
+    /// Stored state revisions
+    RevisionBuffer revisions_;
+    /// Component states
+    std::map<ShortStringHash, ComponentReplicationState> components_;
+};
+
+/// Per-client scene state for delta updates
+class SceneReplicationState
+{
+public:
+    /// Return per-client state for an node, or null if not found
+    NodeReplicationState* FindNode(unsigned id)
+    {
+        std::map<unsigned, NodeReplicationState>::iterator i = nodes_.find(id);
+        if (i != nodes_.end())
+            return &(i->second);
+        else
+            return 0;
+    }
+    
+    /// Clear all stored state
+    void Clear();
+    /// Mark that client acked state up to a specific frame
+    void Acked(unsigned short lastAck);
+    /// Return highest number of revisions between all nodes
+    unsigned GetRevisionCount() const;
+    
+    /// Node states
+    std::map<unsigned, NodeReplicationState> nodes_;
+};

+ 984 - 0
Engine/Network/Server.cpp

@@ -0,0 +1,984 @@
+//
+// Urho3D Engine
+// Copyright (c) 2008-2011 Lasse Öörni
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#include "Precompiled.h"
+#include "Connection.h"
+#include "File.h"
+#include "Log.h"
+#include "Network.h"
+#include "NetworkEvents.h"
+#include "PackageFile.h"
+#include "Profiler.h"
+#include "Protocol.h"
+#include "ProtocolEvents.h"
+#include "Scene.h"
+#include "Server.h"
+#include "StringUtils.h"
+
+#include <set>
+
+#include "DebugNew.h"
+
+// Timeout for closing transferred package files, in milliseconds
+static const int FILE_TIMEOUT = 15 * 1000;
+
+OBJECTTYPESTATIC(Server);
+
+Server::Server(Context* context) :
+    Object(context),
+    netFps_(30),
+    timeAcc_(0),
+    frameNumber_(1),
+    maxSceneRevisions_(100),
+    stayRelevantTime_(30)
+{
+    SubscribeToEvent(E_PEERCONNECTED, HANDLER(Server, HandlePeerConnected));
+    SubscribeToEvent(E_PEERDISCONNECTED, HANDLER(Server, HandlePeerDisconnected));
+}
+
+Server::~Server()
+{
+}
+
+void Server::SetNetFps(int fps)
+{
+    netFps_ = max(fps, 1);
+}
+
+void Server::SetMaxSceneRevisions(int revisions)
+{
+    maxSceneRevisions_ = max(revisions, 3);
+}
+
+void Server::SetStayRelevantTime(int time)
+{
+    stayRelevantTime_ = max(time, 1);
+}
+
+void Server::AddScene(Scene* scene)
+{
+    if (!scene)
+        return;
+    
+    if (HasScene(scene))
+    {
+        LOGWARNING("Scene " + scene->GetName() + " already added to server");
+        return;
+    }
+    
+    scene->SetNetworkMode(NM_SERVER);
+    scenes_.push_back(SharedPtr<Scene>(scene));
+}
+
+void Server::RemoveScene(Scene* scene)
+{
+    if (!scene)
+        return;
+    
+    for (unsigned i = 0; i < scenes_.size(); ++i)
+    {
+        if (scenes_[i] == scene)
+        {
+            VectorBuffer packet;
+            packet.WriteUByte(MSG_JOINREPLY);
+            packet.WriteBool(false);
+            packet.WriteString("The scene is shutting down");
+            
+            // If any clients are connected to this scene, they must leave forcibly
+            for (unsigned j = 0; j < connections_.size(); ++j)
+            {
+                Connection* connection = connections_[j];
+                if (connection->GetScene() == scene)
+                {
+                    connection->SendReliable(packet);
+                    connection->LeftScene();
+                }
+            }
+            
+            // Remove the network mode
+            scene->SetNetworkMode(NM_NONETWORK);
+            scenes_.erase(scenes_.begin() + i);
+            return;
+        }
+    }
+    
+    LOGWARNING("Scene " + scene->GetName() + " not found on server");
+}
+
+bool Server::Start(unsigned short port)
+{
+    timeAcc_ = 0.0f;
+    frameNumber_ = 1;
+    return GetSubsystem<Network>()->StartServer(port);
+}
+
+void Server::Stop()
+{
+    GetSubsystem<Network>()->StopServer();
+}
+
+void Server::Update(float timeStep)
+{
+    PROFILE(UpdateServer);
+    
+    // Process incoming packets from connections (assume that Engine has updated Network, so we do not do that here)
+    for (unsigned i = 0; i < connections_.size(); ++i)
+        HandlePackets(connections_[i]);
+    
+    // Update scenes / send update if enough time passed
+    float netPeriod = 1.0f / netFps_;
+    timeAcc_ += timeStep;
+    if (timeAcc_ >= netPeriod)
+    {
+        // Update simulation of scene(s)
+        for (unsigned i = 0; i < scenes_.size(); ++i)
+            scenes_[i]->Update(netPeriod);
+        
+        // If multiple updates have accumulated because of a slow frame, send just one
+        while (timeAcc_ >= netPeriod)
+            timeAcc_ -= netPeriod;
+        ++frameNumber_;
+        
+        // We have a policy that framenumber 0 means "frame never received", so loop back to 1
+        if (!frameNumber_)
+            ++frameNumber_;
+        
+        // Send update for each connection
+        for (unsigned i = 0; i < connections_.size(); ++i)
+            SendServerUpdate(connections_[i]);
+    }
+    
+    // Remove disconnected clients
+    for (std::vector<SharedPtr<Connection> >::iterator i = connections_.begin(); i != connections_.end();)
+    {
+        if (!(*i)->IsConnected())
+            i = connections_.erase(i);
+        else
+            ++i;
+    }
+    
+    // Close file transfers that have been unused for some time
+    for (std::map<StringHash, ServerFileTransfer>::iterator i = fileTransfers_.begin(); i != fileTransfers_.end();)
+    {
+        std::map<StringHash, ServerFileTransfer>::iterator current = i++;
+        if (current->second.closeTimer_.GetMSec(false) > FILE_TIMEOUT)
+            fileTransfers_.erase(current);
+    }
+}
+
+bool Server::SetClientScene(Connection* connection, Scene* scene)
+{
+    // Check that the scene is under server control
+    if (!HasScene(scene))
+        return false;
+    
+    connection->SetScene(scene);
+    SendSceneInfo(connection);
+    
+    return true;
+}
+
+bool Server::IsRunning() const
+{
+    return GetSubsystem<Network>()->IsServer();
+}
+
+bool Server::HasScene(Scene* scene) const
+{
+    for (unsigned i = 0; i < scenes_.size(); ++i)
+    {
+        if (scenes_[i] == scene)
+            return true;
+    }
+    
+    return false;
+}
+
+unsigned Server::GetNumUsersInScene(Scene* scene) const
+{
+    unsigned users = 0;
+    
+    for (unsigned i = 0; i < connections_.size(); ++i)
+    {
+        if (connections_[i]->GetScene() == scene)
+            ++users;
+    }
+    
+    return users;
+}
+
+void Server::HandlePackets(Connection* connection)
+{
+    VectorBuffer packet;
+    
+    // Process reliable packets first, then unreliable
+    while (connection->ReceiveReliable(packet))
+    {
+        if (!HandleReliablePacket(connection, packet))
+            return;
+    }
+    while (connection->ReceiveUnreliable(packet))
+    {
+        if (!HandleClientUpdate(connection, packet))
+            return;
+    }
+}
+
+void Server::HandlePeerConnected(StringHash eventType, VariantMap& eventData)
+{
+    using namespace PeerConnected;
+    
+    Peer* peer = static_cast<Peer*>(eventData[P_PEER].GetPtr());
+    if (peer->GetPeerType() != PEER_CLIENT)
+        return;
+    
+    // Create a new connection, assign a challenge, then send the challenge message
+    SharedPtr<Connection> connection(new Connection(context_, peer));
+    connections_.push_back(connection);
+    
+    unsigned challenge = GenerateChallenge();
+    connection->SetChallenge(challenge);
+    
+    VectorBuffer packet;
+    packet.WriteUByte(MSG_CHALLENGE);
+    packet.WriteUInt(challenge);
+    connection->SendReliable(packet);
+}
+
+void Server::HandlePeerDisconnected(StringHash eventType, VariantMap& eventData)
+{
+    using namespace PeerDisconnected;
+    
+    Peer* peerPtr = static_cast<Peer*>(eventData[P_PEER].GetPtr());
+    if (peerPtr->GetPeerType() != PEER_CLIENT)
+        return;
+    
+    for (unsigned i = 0; i < connections_.size(); ++i)
+    {
+        Connection* connection = connections_[i];
+        if (connection->GetPeer() == peerPtr)
+        {
+            // Remove if was in a scene
+            connection->LeftScene();
+            return;
+        }
+    }
+}
+
+bool Server::HandleReliablePacket(Connection* connection, VectorBuffer& packet)
+{
+    unsigned char msgID = 0;
+    
+    msgID = packet.ReadUByte();
+    
+    switch (msgID)
+    {
+    case MSG_LOGIN:
+        if (!HandleLogin(connection, packet))
+            return false;
+        else
+            break;
+        
+    case MSG_REQUESTFILE:
+        HandleRequestFile(connection, packet);
+        break;
+        
+    case MSG_JOINSCENE:
+        HandleJoinScene(connection, packet);
+        break;
+        
+    case MSG_FULLUPDATEACK:
+        HandleFullUpdateAck(connection, packet);
+        break;
+        
+    default:
+        Disconnect(connection, false, "Unauthorized message ID " + ToString((int)msgID) + ", disconnecting client");
+        return false;
+    }
+    
+    return true;
+}
+
+bool Server::HandleLogin(Connection* connection, VectorBuffer& packet)
+{
+    connection->SetLoginData(packet.ReadVariantMap());
+    
+    // Send login event and check if any event handler denies access
+    using namespace ClientLogin;
+    
+    // Initialize eventdata with the logindata received
+    VariantMap eventData = connection->GetLoginData();
+    eventData[P_CONNECTION] = (void*)connection;
+    eventData[P_AUTHORIZE] = true;
+    SendEvent(E_CLIENTLOGIN, eventData);
+    
+    if (!eventData[P_AUTHORIZE].GetBool())
+    {
+        Disconnect(connection, false, "Disconnecting unauthorized client");
+        return false;
+    }
+    
+    return true;
+}
+
+void Server::HandleRequestFile(Connection* connection, VectorBuffer& packet)
+{
+    StringHash nameHash = packet.ReadStringHash();
+    int fragmentStart = packet.ReadVLE();
+    int fragmentCount = packet.ReadVLE();
+    
+    // The only files we are willing to transmit are packages associated with scene(s)
+    PackageFile* package = 0;
+    for (unsigned i = 0; i < scenes_.size(); ++i)
+    {
+        const std::vector<SharedPtr<PackageFile> >& packages =  scenes_[i]->GetRequiredPackageFiles();
+        for (unsigned j = 0; j < packages.size(); ++j)
+        {
+            if (packages[j]->GetNameHash() == nameHash)
+            {
+                package = packages[j];
+                break;
+            }
+        }
+    }
+    if (!package)
+    {
+        LOGWARNING("Client " + connection->GetIdentity() + " requested unknown file " + ToString(nameHash));
+        VectorBuffer replyPacket;
+        replyPacket.WriteUByte(MSG_TRANSFERFAILED);
+        replyPacket.WriteStringHash(nameHash);
+        replyPacket.WriteString("File not found");
+        connection->SendReliable(replyPacket);
+        return;
+    }
+    
+    // Open file if not already open
+    File* file = fileTransfers_[nameHash].file_;
+    if (!file)
+    {
+        file = fileTransfers_[nameHash].file_ = new File(context_, package->GetName());
+        if (!file->IsOpen())
+        {
+            LOGERROR("Failed to open package file " + package->GetName() + " for file transfer");
+            VectorBuffer replyPacket;
+            replyPacket.WriteUByte(MSG_TRANSFERFAILED);
+            replyPacket.WriteStringHash(nameHash);
+            replyPacket.WriteString("Could not open file");
+            connection->SendReliable(replyPacket);
+            return;
+        }
+    }
+    
+    fileTransfers_[nameHash].closeTimer_.Reset();
+    
+    // Check that fragment range is valid
+    unsigned fileSize = file->GetSize();
+    int maxFragments = (fileSize - 1) / FILE_FRAGMENT_SIZE + 1;
+    if (fragmentStart + fragmentCount > maxFragments)
+        fragmentCount = maxFragments - fragmentStart;
+    if (fragmentCount <= 0)
+        return;
+    
+    if (!fragmentStart)
+        LOGINFO("Client " + connection->GetIdentity() + " requested file " + ToString(nameHash));
+    
+    // Send the fragments
+    unsigned fragmentOffset = fragmentStart * FILE_FRAGMENT_SIZE;
+    file->Seek(fragmentOffset);
+    
+    for (int i = fragmentStart; i < fragmentStart + fragmentCount; ++i)
+    {
+        fragmentOffset = i * FILE_FRAGMENT_SIZE;
+        unsigned fragmentSize = fileSize - fragmentOffset;
+        if (fragmentSize > FILE_FRAGMENT_SIZE)
+            fragmentSize = FILE_FRAGMENT_SIZE;
+        
+        VectorBuffer fragmentPacket;
+        fragmentPacket.Seek(0);
+        fragmentPacket.WriteUByte(MSG_TRANSFERDATA);
+        fragmentPacket.WriteStringHash(nameHash);
+        fragmentPacket.WriteVLE(i);
+        fragmentPacket.Resize(fragmentPacket.GetPosition() + fragmentSize);
+        file->Read(fragmentPacket.GetModifiableData() + fragmentPacket.GetPosition(), fragmentSize);
+        connection->SendReliable(fragmentPacket);
+    }
+}
+
+void Server::HandleJoinScene(Connection* connection, VectorBuffer& packet)
+{
+    unsigned checksum = packet.ReadUInt();
+    Scene* scene = connection->GetScene();
+    
+    VectorBuffer replyPacket;
+    replyPacket.WriteUByte(MSG_JOINREPLY);
+    
+    if (!scene)
+    {
+        replyPacket.WriteBool(false);
+        replyPacket.WriteString("No scene");
+        LOGINFO("Client " + connection->GetIdentity() + " attempted to join without an assigned scene");
+    }
+    else if (checksum != scene->GetChecksum())
+    {
+        replyPacket.WriteBool(false);
+        replyPacket.WriteString("Scene checksum mismatch");
+        LOGINFO("Client " + connection->GetIdentity() + " checksum mismatch for scene " + scene->GetName());
+    }
+    else
+    {
+        replyPacket.WriteBool(true);
+        connection->JoinedScene();
+    }
+    
+    connection->SendReliable(replyPacket);
+}
+
+void Server::HandleFullUpdateAck(Connection* connection, VectorBuffer& packet)
+{
+    if (connection->GetJoinState() == JS_WAITFORACK)
+    {
+        unsigned short lastFrameNumber = packet.ReadUShort();
+        unsigned short lastFrameAck = packet.ReadUShort();
+        connection->SetFrameNumbers(lastFrameNumber, lastFrameAck);
+        connection->UpdateRoundTripTime(netFps_, frameNumber_);
+        connection->SetJoinState(JS_SENDDELTAS);
+    }
+    else
+        LOGWARNING("Received unexpected full update ack from client " + connection->GetIdentity());
+}
+
+bool Server::HandleClientUpdate(Connection* connection, VectorBuffer& packet)
+{
+    // Disregard unreliable client updates while waiting for the initial scene ack
+    if (connection->GetJoinState() != JS_SENDDELTAS)
+        return true;
+    
+    unsigned short lastFrameNumber = packet.ReadUShort();
+    unsigned short lastFrameAck = packet.ReadUShort();
+    
+    // Check that this packet is not older than the last received (overlap may occur when we transition
+    // between a reliable full update and unreliable delta updates)
+    if (!CheckFrameNumber(lastFrameNumber, connection->GetFrameNumber()))
+        return true;
+    
+    connection->SetFrameNumbers(lastFrameNumber, lastFrameAck);
+    connection->UpdateRoundTripTime(netFps_, frameNumber_);
+    
+    unsigned short previousEventFrameNumber = connection->GetEventFrameNumber();
+    
+    while (!packet.IsEof())
+    {
+        unsigned char msgID = packet.ReadUByte();
+        // The client is only allowed to send a few specific messages in the client update
+        switch (msgID)
+        {
+        case MSG_REMOTEEVENT:
+            {
+                RemoteEvent newEvent;
+                newEvent.Read(packet, false);
+                if (connection->CheckRemoteEventFrame(newEvent, previousEventFrameNumber))
+                    newEvent.Dispatch(connection, connection->GetScene());
+            }
+            break;
+            
+        case MSG_REMOTENODEEVENT:
+            {
+                RemoteEvent newEvent;
+                newEvent.Read(packet, true);
+                if (connection->CheckRemoteEventFrame(newEvent, previousEventFrameNumber))
+                    newEvent.Dispatch(connection, connection->GetScene());
+            }
+            break;
+            
+        case MSG_CONTROLS:
+            {
+                Controls newControls;
+                newControls.buttons_ = packet.ReadUInt();
+                newControls.yaw_ = packet.ReadFloat();
+                newControls.pitch_ = packet.ReadFloat();
+                newControls.extraData_ = packet.ReadVariantMap();
+                connection->SetControls(newControls);
+                connection->SetPosition(packet.ReadVector3());
+                
+                using namespace ClientControls;
+                
+                // Initialize event parameters with possible extra controls
+                VariantMap eventData = newControls.extraData_;
+                eventData[P_CONNECTION] = (void*)connection;
+                eventData[P_FRAMENUMBER] = connection->GetFrameNumber();
+                eventData[P_BUTTONS] = newControls.buttons_;
+                eventData[P_YAW] = newControls.yaw_;
+                eventData[P_PITCH] = newControls.pitch_;
+                SendEvent(E_CLIENTCONTROLS, eventData);
+                break;
+            }
+            
+        default:
+            Disconnect(connection, false, "Unauthorized message ID " + ToString((int)msgID) + ", disconnecting client");
+            return false;
+        }
+    }
+    
+    return true;
+}
+
+void Server::SendSceneInfo(Connection* connection)
+{
+    Scene* scene = connection->GetScene();
+    if (!scene)
+        return;
+    
+    VectorBuffer packet;
+    packet.WriteUByte(MSG_SCENEINFO);
+    
+    // Write scene name, number of users and update rate
+    packet.WriteString(scene->GetName());
+    packet.WriteVLE(GetNumUsersInScene(scene));
+    packet.WriteInt(netFps_);
+    
+    // Write source file name & required packages
+    const std::vector<SharedPtr<PackageFile> >& requiredPackages = scene->GetRequiredPackageFiles();
+    packet.WriteString(scene->GetFileName());
+    packet.WriteVLE(requiredPackages.size());
+    for (unsigned i = 0; i < requiredPackages.size(); ++i)
+    {
+        PackageFile* package = requiredPackages[i];
+        packet.WriteString(package->GetName());
+        packet.WriteUInt(package->GetTotalSize());
+        packet.WriteUInt(package->GetChecksum());
+    }
+    
+    connection->SendReliable(packet);
+}
+
+void Server::SendFullUpdate(Connection* connection)
+{
+    PROFILE(SendFullUpdate);
+    
+    Scene* scene = connection->GetScene();
+    if (!scene)
+        return;
+    
+    // Clear all scene revision data so that we write a full update
+    connection->ClearSceneState();
+    
+    VectorBuffer packet;
+    packet.WriteUByte(MSG_FULLUPDATE);
+    WriteNetUpdate(connection, packet);
+    connection->SendReliable(packet);
+    
+    // All unacked remote events were sent reliably, so clear them
+    connection->ClearRemoteEvents();
+    connection->SetJoinState(JS_WAITFORACK);
+    
+    LOGDEBUG("Initial scene: " + ToString(packet.GetSize()) + " bytes");
+}
+
+void Server::SendServerUpdate(Connection* connection)
+{
+    PROFILE(SendServerUpdate);
+    
+    Scene* scene = connection->GetScene();
+    JoinState state = connection->GetJoinState();
+    if ((!scene) || (state < JS_SENDFULLUPDATE) || (state == JS_WAITFORACK))
+        return;
+    
+    // Purge states and events which are older than last acked, and expired remote events
+    connection->PurgeAckedSceneState();
+    connection->PurgeAckedRemoteEvents(frameNumber_);
+    
+    // If already have too many revisions stored, fall back to the initial scene sending (reliable)
+    if (connection->GetSceneState().GetRevisionCount() >= maxSceneRevisions_)
+    {
+        LOGWARNING("Too many scene revisions for client " + connection->GetIdentity() + ", falling back to initial scene send");
+        state = JS_SENDFULLUPDATE;
+    }
+    
+    // Send initial scene as reliable
+    if (state == JS_SENDFULLUPDATE)
+    {
+        SendFullUpdate(connection);
+        return;
+    }
+    
+    VectorBuffer packet;
+    WriteNetUpdate(connection, packet);
+    connection->SendUnreliable(packet);
+    
+    //LOGDEBUG("Delta: " + ToString(packet.GetSize()) + " Revisions: " +
+    //    ToString(connection->GetSceneState().GetRevisionCount()) + " Events: " +
+    //    ToString(connection->GetUnackedRemoteEvents().size()));
+}
+
+unsigned Server::GenerateChallenge() const
+{
+    return (rand() & 32767) | ((rand() & 32767) << 15) | ((rand() & 32767) << 30);
+}
+
+void Server::Disconnect(Connection* connection, bool forced, const std::string& logMessage)
+{
+    LOGERROR(logMessage + " " + connection->GetIdentity());
+    
+    if (forced)
+        connection->forceDisconnect();
+    else
+        connection->Disconnect();
+    
+    // Send event
+    using namespace ClientDisconnected;
+    
+    VariantMap eventData;
+    eventData[P_CONNECTION] = (void*)connection;
+    SendEvent(E_CLIENTDISCONNECTED, eventData);
+}
+
+void Server::WriteNetUpdate(Connection* connection, Serializer& dest)
+{
+    PROFILE(WriteNetUpdate);
+    
+    Scene* scene = connection->GetScene();
+    SceneReplicationState& sceneState = connection->GetSceneState();
+    
+    // Write frame numbers
+    dest.WriteUShort(frameNumber_);
+    dest.WriteUShort(connection->GetFrameNumber());
+    
+    VectorBuffer emptyBaseRevision;
+    VectorBuffer propertyBuffer;
+    VectorBuffer newBuffer;
+    VectorBuffer removeBuffer;
+    VectorBuffer updateBuffer;
+    VectorBuffer newRevision;
+    
+    /*
+    
+    // Find relevant nodes for this client
+    std::set<unsigned> relevantNodes;
+    GetRelevantNodes(connection, relevantNodes);
+    
+    {
+        // Go through the scene and see which nodes are new and which have been removed
+        const std::map<unsigned, SharedPtr<Node> >& nodes = scene->GetAllNodes();
+        std::set<unsigned> processedNodes;
+        for (std::map<unsigned, SharedPtr<Node> >::const_iterator i = nodes.begin(); i != nodes.end(); ++i)
+        {
+            // If we reach the local node ID's, break
+            if (i->first >= FIRST_LOCAL_ID)
+                break;
+            
+            processedNodes.insert(i->first);
+            
+            bool relevant = relevantNodes.find(i->first) != relevantNodes.end();
+            
+            Node* node = i->second;
+            NodeReplicationState* nodeState = sceneState.findNode(i->first);
+            if (!nodeState)
+            {
+                // If client does not have this node and it is not relevant, skip
+                if (!relevant)
+                    continue;
+                
+                nodeState = &sceneState.nodes_[i->first];
+                nodeState->Created(frameNumber_);
+                nodeState->stayRelevantTime_ = stayRelevantTime_;
+            }
+            else
+            {
+                // If nodestate exists, check relevancy timer and refresh it if necessary
+                if (relevant)
+                    nodeState->stayRelevantTime_ = stayRelevantTime_;
+                else if (nodeState->stayRelevantTime_ > 0)
+                {
+                    --nodeState->stayRelevantTime_;
+                    relevant = true;
+                }
+                else
+                    // If node is not relevant, and relevancy timer has expired, do not check for changes
+                    continue;
+                
+                if (!nodeState->exists_)
+                    nodeState->Created(frameNumber_);
+            }
+            
+            // Check components of this node
+            const std::vector<SharedPtr<Component> >& components = node->GetComponents();
+            std::set<ShortStringHash> processedComponents;
+            for (std::vector<SharedPtr<Component> >::const_iterator j = components.begin(); j != components.end(); ++j)
+            {
+                Component* component = *j;
+                if (!component->checkSync(connection))
+                    continue;
+                
+                ShortStringHash combinedHash = component->GetCombinedHash();
+                ComponentReplicationState* componentState = nodeState->findComponent(combinedHash);
+                
+                if (!componentState)
+                {
+                    componentState = &nodeState->components_[combinedHash];
+                    componentState->Created(frameNumber_);
+                }
+                else if (!componentState->exists_)
+                    componentState->Created(frameNumber_);
+                
+                processedComponents.insert(combinedHash);
+            }
+            
+            // Check components that have been removed
+            for (std::map<ShortStringHash, ComponentReplicationState>::iterator j = nodeState->components_.begin();
+                j != nodeState->components_.end(); ++j)
+            {
+                if (j->second.exists_)
+                {
+                    if (processedComponents.find(j->first) == processedComponents.end())
+                        j->second.Removed(frameNumber_);
+                }
+            }
+        }
+        
+        // Check nodes that have been removed
+        for (std::map<unsigned, NodeReplicationState>::iterator i = sceneState.nodes_.begin();
+            i != sceneState.nodes_.end(); ++i)
+        {
+            if (i->second.exists_)
+            {
+                if (processedNodes.find(i->first) == processedNodes.end())
+                    i->second.Removed(frameNumber_);
+            }
+        }
+    }
+    
+    {
+        // Now go through the replication state again and build commands
+        for (std::map<unsigned, NodeReplicationState>::iterator i = sceneState.nodes_.begin();
+            i != sceneState.nodes_.end(); ++i)
+        {
+            Node* node = scene->GetNode(i->first);
+            NodeReplicationState& nodeState = i->second;
+            // Create
+            if ((nodeState.createdFrame_) && (node))
+            {
+                dest.WriteUByte(MSG_CREATEENTITY);
+                dest.WriteUShort(node->GetID());
+                dest.WriteString(node->GetName());
+                dest.WriteUByte(GetClientNetFlags(connection, node, node->GetNetFlags()));
+                dest.WriteVLE(node->GetGroupFlags());
+                
+                // Write a full update of node properties
+                newRevision.Seek(0);
+                if (node->WriteNetUpdate(dest, newRevision, emptyBaseRevision, info))
+                    nodeState.revisions_.Commit(frameNumber_, newRevision);
+                
+                // Write a full update of all components that should be synced
+                const std::vector<SharedPtr<Component> >& components = node->GetComponents();
+                unsigned newComponents = 0;
+                newBuffer.Seek(0);
+                
+                for (std::vector<SharedPtr<Component> >::const_iterator j = components.begin(); j != components.end(); ++j)
+                {
+                    Component* component = *j;
+                    if (!component->checkSync(connection))
+                        continue;
+                    
+                    ComponentReplicationState& componentState = nodeState.components_[component->GetCombinedHash()];
+                    newBuffer.WriteShortStringHash(component->GetType());
+                    newBuffer.WriteString(component->GetName());
+                    newBuffer.WriteUByte(GetClientNetFlags(connection, node, component->GetNetFlags()));
+                    newRevision.Seek(0);
+                    component->WriteNetUpdate(newBuffer, newRevision, emptyBaseRevision, info);
+                    componentState.revisions_.Commit(frameNumber_, newRevision);
+                    ++newComponents;
+                }
+                
+                dest.WriteVLE(newComponents);
+                // Check if any components were actually written
+                if (newComponents)
+                    dest.Write(newBuffer.GetData(), newBuffer.GetPosition());
+            }
+            // Remove
+            else if (nodeState.removedFrame_)
+            {
+                dest.WriteUByte(MSG_REMOVEENTITY);
+                dest.WriteUShort(i->first);
+            }
+            // Update
+            else if (node)
+            {
+                // If node's update timer has expired (it is not relevant), do not write updates
+                if (nodeState.stayRelevantTime_ <= 0)
+                {
+                    // However, we must be careful to see what to do when the node becomes relevant again
+                    // If node has unacked property or component revisions, must forget all of them
+                    if (nodeState.HasUnAcked(connection->GetFrameAck()))
+                    {
+                        nodeState.revisions_.clear();
+                        for (std::map<ShortStringHash, ComponentReplicationState>::iterator j = nodeState.components_.begin();
+                            j != nodeState.components_.end(); ++j)
+                            j->second.revisions_.clear();
+                    }
+                    continue;
+                }
+                
+                // Divide update types into separate buffers, then see which of them got data
+                // (if there are no updates, then this node does not need to write anything into the final stream)
+                unsigned newComponents = 0;
+                unsigned removedComponents = 0;
+                unsigned updatedComponents = 0;
+                
+                propertyBuffer.Seek(0);
+                newBuffer.Seek(0);
+                removeBuffer.Seek(0);
+                updateBuffer.Seek(0);
+                
+                unsigned char msgID = MSG_UPDATEENTITY;
+                
+                // Property update
+                Deserializer& baseRevision = nodeState.revisions_.GetBase();
+                newRevision.Seek(0);
+                if (node->WriteNetUpdate(propertyBuffer, newRevision, baseRevision, info))
+                {
+                    nodeState.revisions_.Commit(frameNumber_, newRevision);
+                    msgID |= UPD_PROPERTIES;
+                }
+                
+                // Component create/remove/update
+                for (std::map<ShortStringHash, ComponentReplicationState>::iterator j = nodeState.components_.begin();
+                    j != nodeState.components_.end(); ++j)
+                {
+                    Component* component = node->GetComponent(j->first.mData);
+                    ComponentReplicationState& componentState = j->second;
+                    // Create
+                    if ((componentState.createdFrame_) && (component))
+                    {
+                        newBuffer.WriteShortStringHash(component->GetType());
+                        newBuffer.WriteString(component->GetName());
+                        newBuffer.WriteUByte(GetClientNetFlags(connection, node, component->GetNetFlags()));
+                        newRevision.Seek(0);
+                        component->WriteNetUpdate(newBuffer, newRevision, emptyBaseRevision, info);
+                        componentState.revisions_.Commit(frameNumber_, newRevision);
+                        msgID |= UPD_NEWCOMPONENTS;
+                        ++newComponents;
+                    }
+                    // Remove
+                    else if (componentState.removedFrame_)
+                    {
+                        msgID |= UPD_REMOVECOMPONENTS;
+                        ++removedComponents;
+                        removeBuffer.WriteShortStringHash(j->first);
+                    }
+                    // Update
+                    else if (component)
+                    {
+                        // Prepare to rewind buffer in case component writes nothing meaningful
+                        unsigned oldPos = updateBuffer.GetPosition();
+                        updateBuffer.WriteShortStringHash(component->GetCombinedHash());
+                        newRevision.Seek(0);
+                        Deserializer& baseRevision = componentState.revisions_.GetBase();
+                        if (component->WriteNetUpdate(updateBuffer, newRevision, baseRevision, info))
+                        {
+                            componentState.revisions_.Commit(frameNumber_, newRevision);
+                            msgID |= UPD_UPDATECOMPONENTS;
+                            ++updatedComponents;
+                        }
+                        else
+                            updateBuffer.Seek(oldPos);
+                    }
+                }
+                
+                // Now write each buffer if there was some data
+                if (msgID != MSG_UPDATEENTITY)
+                {
+                    dest.WriteUByte(msgID);
+                    dest.WriteUShort(node->GetID());
+                    
+                    if (msgID & UPD_PROPERTIES)
+                        dest.Write(propertyBuffer.GetData(), propertyBuffer.GetPosition());
+                    if (msgID & UPD_NEWCOMPONENTS)
+                    {
+                        dest.WriteVLE(newComponents);
+                        dest.Write(newBuffer.GetData(), newBuffer.GetPosition());
+                    }
+                    if (msgID & UPD_REMOVECOMPONENTS)
+                    {
+                        dest.WriteVLE(removedComponents);
+                        dest.Write(removeBuffer.GetData(), removeBuffer.GetPosition());
+                    }
+                    if (msgID & UPD_UPDATECOMPONENTS)
+                    {
+                        dest.WriteVLE(updatedComponents);
+                        dest.Write(updateBuffer.GetData(), updateBuffer.GetPosition());
+                    }
+                }
+            }
+        }
+    }
+    
+    */
+    
+    // Append unacked remote events
+    const std::vector<RemoteEvent>& unackedEvents = connection->GetUnackedRemoteEvents();
+    for (std::vector<RemoteEvent>::const_iterator i = unackedEvents.begin(); i != unackedEvents.end(); ++i)
+    {
+        dest.WriteUByte(i->nodeID_ ? MSG_REMOTENODEEVENT : MSG_REMOTEEVENT);
+        i->Write(dest);
+    }
+}
+
+void Server::GetRelevantNodes(Connection* connection, std::set<unsigned>& dest) const
+{
+    // Generate just the raw set of relevant nodes based on their owner, distance and references. A node might need
+    // to stay relevant because it has unacked changes, or has time left in its relevancy timer, but that is checked in
+    // WriteNetUpdate()
+    PROFILE(GetRelevantNodes);
+    
+    dest.clear();
+    
+    Scene* scene = connection->GetScene();
+    const std::map<unsigned, Node*>& nodes = scene->GetAllNodes();
+    const Vector3& clientPos = connection->GetPosition();
+    
+    for (std::map<unsigned, Node*>::const_iterator i = nodes.begin(); i != nodes.end(); ++i)
+    {
+        // Stop when local node ID range reached
+        if (i->first >= FIRST_LOCAL_ID)
+            break;
+        
+        Node* node = i->second;
+        
+        // If node is not owned by client and max. update distance has been defined, check it
+        if (node->GetOwner() != connection)
+        {
+            //float maxDistance = node->GetNetUpdateDistance();
+            float maxDistance = 100.0f; /// \todo Add max. update distance to Node
+            if (maxDistance > 0.0f)
+            {
+                if ((node->GetWorldPosition() - clientPos).GetLengthSquared() > maxDistance * maxDistance)
+                    continue;
+            }
+        }
+        
+        // Node is relevant. Now also find its dependencies
+        dest.insert(i->first);
+        /// \todo Implement getting the dependencies from a node
+        //GetNodeDependencies(connection, node, dest);
+    }
+}

+ 140 - 0
Engine/Network/Server.h

@@ -0,0 +1,140 @@
+//
+// Urho3D Engine
+// Copyright (c) 2008-2011 Lasse Öörni
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+#include "Object.h"
+#include "Timer.h"
+
+#include <set>
+
+class Connection;
+class Network;
+class Scene;
+class VectorBuffer;
+
+/// Server-side ongoing download, with a timer for closing the file if unused
+struct ServerFileTransfer
+{
+    SharedPtr<File> file_;
+    Timer closeTimer_;
+};
+
+/// Multiplayer server subsystem
+class Server : public Object
+{
+    OBJECT(Server);
+    
+public:
+    /// Construct with network subsystem pointer
+    Server(Context* context);
+    /// Destruct
+    virtual ~Server();
+    
+    /// Set network updates (number of server frames) per second
+    void SetNetFps(int fps);
+    /// Set maximum unacked scene revisions stored per connection
+    void SetMaxSceneRevisions(int revisions);
+    /// Set delay for node becoming non-relevant, measured in server frames (prevent relevant/non-relevant state "flicker")
+    void SetStayRelevantTime(int time);
+    /// Add a scene to be provided over the network
+    void AddScene(Scene* scene);
+    /// Remove a scene
+    void RemoveScene(Scene* scene);
+    /// Start server in the specified port
+    bool Start(unsigned short port);
+    /// Stop server
+    void Stop();
+    /// Send and receive packets and update scene(s)
+    void Update(float timeStep);
+    /// Assign client to a scene
+    bool SetClientScene(Connection* connection, Scene* scene);
+    
+    /// Return network updates per second
+    int GetNetFps() const { return netFps_; }
+    /// Return maximum unacked scene revisions stored per connection
+    int GetMaxSceneRevisions() const { return maxSceneRevisions_; }
+    /// Return delay for node becoming non-relevant
+    int GetStayRelevantTime() const { return stayRelevantTime_; }
+    /// Return whether is running on a port
+    bool IsRunning() const;
+    /// Return whether has a specific scene
+    bool HasScene(Scene* scene) const;
+    /// Return all scenes
+    const std::vector<SharedPtr<Scene> >& GetScenes() const { return scenes_; }
+    /// Return all connections
+    const std::vector<SharedPtr<Connection> >& GetConnections() const { return connections_; }
+    /// Return number of clients in scene
+    unsigned GetNumUsersInScene(Scene* scene) const;
+    
+private:
+    /// Receive and handle packets from a connection
+    void HandlePackets(Connection* connection);
+    /// Handle peer connected event
+    void HandlePeerConnected(StringHash eventType, VariantMap& eventData);
+    /// Handle peer disconnected event
+    void HandlePeerDisconnected(StringHash eventType, VariantMap& eventData);
+    /// Handle a reliable client packet. Return true if should continue receiving packets
+    bool HandleReliablePacket(Connection* connection, VectorBuffer& packet);
+    /// Handle a login packet. Return true if client was authorized
+    bool HandleLogin(Connection* connection, VectorBuffer& packet);
+    /// Handle a file request packet
+    void HandleRequestFile(Connection* connection, VectorBuffer& packet);
+    /// Handle a join scene packet
+    void HandleJoinScene(Connection* connection, VectorBuffer& packet);
+    /// Handle a full update ack packet
+    void HandleFullUpdateAck(Connection* connection, VectorBuffer& packet);
+    /// Handle a client update packet. Return true if should continue receiving packets
+    bool HandleClientUpdate(Connection* connection, VectorBuffer& packet);
+    /// Send scene info to the client
+    void SendSceneInfo(Connection* connection);
+    /// Send full scene update to the client
+    void SendFullUpdate(Connection* connection);
+    /// Send a scene update to the client
+    void SendServerUpdate(Connection* connection);
+    /// Generate a challenge value for the client
+    unsigned GenerateChallenge() const;
+    /// Disconnect client either forcibly or benevolently
+    void Disconnect(Connection* connection, bool forced, const std::string& logMessage);
+    /// Generate a scene update. If connection has no stored scene revisions, a full update will be written
+    void WriteNetUpdate(Connection* connection, Serializer& dest);
+    /// Return relevant node IDs for the client
+    void GetRelevantNodes(Connection* connection, std::set<unsigned>& dest) const;
+    
+    /// Scenes
+    std::vector<SharedPtr<Scene> > scenes_;
+    /// Client connections
+    std::vector<SharedPtr<Connection> > connections_;
+    /// Ongoing file downloads
+    std::map<StringHash, ServerFileTransfer> fileTransfers_;
+    /// Network updates per second
+    int netFps_;
+    /// Network update time accumulator
+    float timeAcc_;
+    /// Current server framenumber
+    unsigned short frameNumber_;
+    /// Maximum scene revisions to store per connection
+    unsigned maxSceneRevisions_;
+    /// Delay for node becoming non-relevant, in server frames
+    int stayRelevantTime_;
+};

+ 1 - 1
Engine/Resource/ResourceCache.h

@@ -97,7 +97,7 @@ public:
     /// Return added resource load paths
     /// Return added resource load paths
     const std::vector<std::string>& GetResourcePaths() const { return resourcePaths_; }
     const std::vector<std::string>& GetResourcePaths() const { return resourcePaths_; }
     /// Return added package files
     /// Return added package files
-    const std::vector<SharedPtr<PackageFile> >& getPackageFiles() const { return packages_; }
+    const std::vector<SharedPtr<PackageFile> >& GetPackageFiles() const { return packages_; }
     /// Template version of returning a resource by name
     /// Template version of returning a resource by name
     template <class T> T* GetResource(const std::string& name);
     template <class T> T* GetResource(const std::string& name);
     /// Template version of returning a resource by name hash
     /// Template version of returning a resource by name hash

+ 1 - 1
Engine/Scene/Node.cpp

@@ -723,7 +723,7 @@ Node* Node::CreateChild(unsigned id, bool local)
     
     
     // If zero ID specified, let the scene assign
     // If zero ID specified, let the scene assign
     if (scene_)
     if (scene_)
-        newNode->SetID(id ? id : scene_->GetFreeNodeID(local));
+        newNode->SetID(id ? id : scene_->GetFreeunsigned(local));
     else
     else
         newNode->SetID(id);
         newNode->SetID(id);
     
     

+ 95 - 36
Engine/Scene/Scene.cpp

@@ -27,6 +27,7 @@
 #include "CoreEvents.h"
 #include "CoreEvents.h"
 #include "File.h"
 #include "File.h"
 #include "Log.h"
 #include "Log.h"
+#include "PackageFile.h"
 #include "Profiler.h"
 #include "Profiler.h"
 #include "Scene.h"
 #include "Scene.h"
 #include "SceneEvents.h"
 #include "SceneEvents.h"
@@ -45,11 +46,12 @@ Scene::Scene(Context* context) :
     nonLocalComponentID_(FIRST_NONLOCAL_ID),
     nonLocalComponentID_(FIRST_NONLOCAL_ID),
     localNodeID_(FIRST_LOCAL_ID),
     localNodeID_(FIRST_LOCAL_ID),
     localComponentID_(FIRST_LOCAL_ID),
     localComponentID_(FIRST_LOCAL_ID),
+    checksum_(0),
     active_(true),
     active_(true),
     asyncLoading_(false)
     asyncLoading_(false)
 {
 {
     // Assign an ID to self so that nodes can refer to this node as a parent
     // Assign an ID to self so that nodes can refer to this node as a parent
-    SetID(GetFreeNodeID(false));
+    SetID(GetFreeunsigned(false));
     NodeAdded(this);
     NodeAdded(this);
     
     
     SubscribeToEvent(E_UPDATE, HANDLER(Scene, HandleUpdate));
     SubscribeToEvent(E_UPDATE, HANDLER(Scene, HandleUpdate));
@@ -90,7 +92,7 @@ bool Scene::Load(Deserializer& source)
     // Load the whole scene, then perform post-load if successfully loaded
     // Load the whole scene, then perform post-load if successfully loaded
     if (Node::Load(source))
     if (Node::Load(source))
     {
     {
-        PostLoad();
+        FinishLoading(&source);
         return true;
         return true;
     }
     }
     else
     else
@@ -114,15 +116,42 @@ bool Scene::LoadXML(const XMLElement& source)
     StopAsyncLoading();
     StopAsyncLoading();
     
     
     // Load the whole scene, then perform post-load if successfully loaded
     // Load the whole scene, then perform post-load if successfully loaded
+    // Note: the scene filename and checksum can not be set, as we only used an XML element
     if (Node::LoadXML(source))
     if (Node::LoadXML(source))
     {
     {
-        PostLoad();
+        FinishLoading(0);
         return true;
         return true;
     }
     }
     else
     else
         return false;
         return false;
 }
 }
 
 
+void Scene::Update(float timeStep)
+{
+    if (asyncLoading_)
+    {
+        UpdateAsyncLoading();
+        return;
+    }
+    
+    PROFILE(UpdateScene);
+    
+    using namespace SceneUpdate;
+    
+    VariantMap eventData;
+    eventData[P_SCENE] = (void*)this;
+    eventData[P_TIMESTEP] = timeStep;
+    
+    // Update variable timestep logic
+    SendEvent(E_SCENEUPDATE, eventData);
+    
+    // Update scene subsystems. If a physics world is present, it will be updated, triggering fixed timestep logic updates
+    SendEvent(E_SCENESUBSYSTEMUPDATE, eventData);
+    
+    // Post-update variable timestep logic
+    SendEvent(E_SCENEPOSTUPDATE, eventData);
+}
+
 bool Scene::LoadXML(Deserializer& source)
 bool Scene::LoadXML(Deserializer& source)
 {
 {
     StopAsyncLoading();
     StopAsyncLoading();
@@ -131,7 +160,14 @@ bool Scene::LoadXML(Deserializer& source)
     if (!xml->Load(source))
     if (!xml->Load(source))
         return false;
         return false;
     
     
-    return LoadXML(xml->GetRootElement());
+    // Load the whole scene, then perform post-load if successfully loaded
+    if (Node::LoadXML(xml->GetRootElement()))
+    {
+        FinishLoading(&source);
+        return true;
+    }
+    else
+        return false;
 }
 }
 
 
 bool Scene::SaveXML(Serializer& dest)
 bool Scene::SaveXML(Serializer& dest)
@@ -162,6 +198,7 @@ bool Scene::LoadAsync(File* file)
     }
     }
     
     
     // Clear the previous scene and load the root level components first
     // Clear the previous scene and load the root level components first
+    Clear();
     if (!Node::Load(*file, false))
     if (!Node::Load(*file, false))
         return false;
         return false;
     
     
@@ -191,6 +228,7 @@ bool Scene::LoadAsyncXML(File* file)
         return false;
         return false;
     
     
     // Clear the previous scene and load the root level components first
     // Clear the previous scene and load the root level components first
+    Clear();
     XMLElement rootElement = xmlFile->GetRootElement();
     XMLElement rootElement = xmlFile->GetRootElement();
     if (!Node::LoadXML(rootElement, false))
     if (!Node::LoadXML(rootElement, false))
         return false;
         return false;
@@ -198,7 +236,7 @@ bool Scene::LoadAsyncXML(File* file)
     // Then prepare for loading all root level child nodes in the async update
     // Then prepare for loading all root level child nodes in the async update
     XMLElement childNodeElement = rootElement.GetChildElement("node");
     XMLElement childNodeElement = rootElement.GetChildElement("node");
     asyncLoading_ = true;
     asyncLoading_ = true;
-    asyncProgress_.file_.Reset();
+    asyncProgress_.file_ = file;
     asyncProgress_.xmlFile_ = xmlFile;
     asyncProgress_.xmlFile_ = xmlFile;
     asyncProgress_.xmlElement_ = childNodeElement;
     asyncProgress_.xmlElement_ = childNodeElement;
     asyncProgress_.loadedNodes_ = 0;
     asyncProgress_.loadedNodes_ = 0;
@@ -222,32 +260,6 @@ void Scene::StopAsyncLoading()
     asyncProgress_.xmlElement_ = XMLElement();
     asyncProgress_.xmlElement_ = XMLElement();
 }
 }
 
 
-void Scene::Update(float timeStep)
-{
-    if (asyncLoading_)
-    {
-        UpdateAsyncLoading();
-        return;
-    }
-    
-    PROFILE(UpdateScene);
-    
-    using namespace SceneUpdate;
-    
-    VariantMap eventData;
-    eventData[P_SCENE] = (void*)this;
-    eventData[P_TIMESTEP] = timeStep;
-    
-    // Update variable timestep logic
-    SendEvent(E_SCENEUPDATE, eventData);
-    
-    // Update scene subsystems. If a physics world is present, it will be updated, triggering fixed timestep logic updates
-    SendEvent(E_SCENESUBSYSTEMUPDATE, eventData);
-    
-    // Post-update variable timestep logic
-    SendEvent(E_SCENEPOSTUPDATE, eventData);
-}
-
 void Scene::SetActive(bool enable)
 void Scene::SetActive(bool enable)
 {
 {
     active_ = enable;
     active_ = enable;
@@ -258,6 +270,42 @@ void Scene::SetNetworkMode(NetworkMode mode)
     networkMode_ = mode;
     networkMode_ = mode;
 }
 }
 
 
+void Scene::Clear()
+{
+    RemoveAllChildren();
+    RemoveAllComponents();
+    fileName_ = std::string();
+    checksum_ = 0;
+}
+
+void Scene::ClearNonLocal()
+{
+    // Because node removal can remove arbitrary other nodes, can not iterate. Instead loop until the first node is local,
+    // or the map is empty
+    while ((allNodes_.size()) && (allNodes_.begin()->first < FIRST_LOCAL_ID))
+        allNodes_.begin()->second->Remove();
+}
+
+void Scene::AddRequiredPackageFile(PackageFile* file)
+{
+    if (file)
+        requiredPackageFiles_.push_back(SharedPtr<PackageFile>(file));
+}
+
+void Scene::ClearRequiredPackageFiles()
+{
+    requiredPackageFiles_.clear();
+}
+
+void Scene::ResetOwner(Connection* owner)
+{
+    for (std::map<unsigned, Node*>::iterator i = allNodes_.begin(); i != allNodes_.end(); ++i)
+    {
+        if (i->second->GetOwner() == owner)
+            i->second->SetOwner(0);
+    }
+}
+
 Node* Scene::GetNodeByID(unsigned id) const
 Node* Scene::GetNodeByID(unsigned id) const
 {
 {
     std::map<unsigned, Node*>::const_iterator i = allNodes_.find(id);
     std::map<unsigned, Node*>::const_iterator i = allNodes_.find(id);
@@ -284,7 +332,7 @@ float Scene::GetAsyncProgress() const
         return (float)asyncProgress_.loadedNodes_ / (float)asyncProgress_.totalNodes_;
         return (float)asyncProgress_.loadedNodes_ / (float)asyncProgress_.totalNodes_;
 }
 }
 
 
-unsigned Scene::GetFreeNodeID(bool local)
+unsigned Scene::GetFreeunsigned(bool local)
 {
 {
     if (!local)
     if (!local)
     {
     {
@@ -415,12 +463,12 @@ void Scene::UpdateAsyncLoading()
         }
         }
         
         
         // Read one child node either from binary or XML
         // Read one child node either from binary or XML
-        if ((asyncProgress_.file_) && (!asyncProgress_.file_->IsEof()))
+        if (!asyncProgress_.xmlFile_)
         {
         {
             Node* newNode = CreateChild(asyncProgress_.file_->ReadUInt(), false);
             Node* newNode = CreateChild(asyncProgress_.file_->ReadUInt(), false);
             newNode->Load(*asyncProgress_.file_);
             newNode->Load(*asyncProgress_.file_);
         }
         }
-        if (asyncProgress_.xmlElement_)
+        else
         {
         {
             Node* newNode = CreateChild(asyncProgress_.xmlElement_.GetInt("id"), false);
             Node* newNode = CreateChild(asyncProgress_.xmlElement_.GetInt("id"), false);
             newNode->LoadXML(asyncProgress_.xmlElement_);
             newNode->LoadXML(asyncProgress_.xmlElement_);
@@ -430,7 +478,7 @@ void Scene::UpdateAsyncLoading()
         ++asyncProgress_.loadedNodes_;
         ++asyncProgress_.loadedNodes_;
         
         
         // Break if time limit exceeded, so that we keep sufficient FPS
         // Break if time limit exceeded, so that we keep sufficient FPS
-        if (asyncLoadTimer.GetMSec(true) >= ASYNC_LOAD_MAX_MSEC)
+        if (asyncLoadTimer.GetMSec(false) >= ASYNC_LOAD_MAX_MSEC)
             break;
             break;
     }
     }
     
     
@@ -446,7 +494,7 @@ void Scene::UpdateAsyncLoading()
 
 
 void Scene::FinishAsyncLoading()
 void Scene::FinishAsyncLoading()
 {
 {
-    PostLoad();
+    FinishLoading(asyncProgress_.file_);
     StopAsyncLoading();
     StopAsyncLoading();
     
     
     using namespace AsyncLoadFinished;
     using namespace AsyncLoadFinished;
@@ -456,6 +504,17 @@ void Scene::FinishAsyncLoading()
     SendEvent(E_ASYNCLOADFINISHED, eventData);
     SendEvent(E_ASYNCLOADFINISHED, eventData);
 }
 }
 
 
+void Scene::FinishLoading(Deserializer* source)
+{
+    PostLoad();
+    
+    if (source)
+    {
+        fileName_ = source->GetName();
+        checksum_ = source->GetChecksum();
+    }
+}
+
 void RegisterSceneLibrary(Context* context)
 void RegisterSceneLibrary(Context* context)
 {
 {
     Node::RegisterObject(context);
     Node::RegisterObject(context);

+ 32 - 3
Engine/Scene/Scene.h

@@ -27,6 +27,7 @@
 #include "XMLElement.h"
 #include "XMLElement.h"
 
 
 class File;
 class File;
+class PackageFile;
 
 
 /// First replicated node/component ID
 /// First replicated node/component ID
 static const unsigned FIRST_NONLOCAL_ID = 0x1;
 static const unsigned FIRST_NONLOCAL_ID = 0x1;
@@ -82,6 +83,8 @@ public:
     /// Load from XML data. Return true if successful
     /// Load from XML data. Return true if successful
     virtual bool LoadXML(const XMLElement& source);
     virtual bool LoadXML(const XMLElement& source);
     
     
+    /// Update scene
+    void Update(float timeStep);
     /// Load from an XML file. Return true if successful
     /// Load from an XML file. Return true if successful
     bool LoadXML(Deserializer& source);
     bool LoadXML(Deserializer& source);
     /// Save to an XML file. Return true if successful
     /// Save to an XML file. Return true if successful
@@ -92,12 +95,20 @@ public:
     bool LoadAsyncXML(File* file);
     bool LoadAsyncXML(File* file);
     /// Stop asynchronous loading
     /// Stop asynchronous loading
     void StopAsyncLoading();
     void StopAsyncLoading();
-    /// Update scene
-    void Update(float timeStep);
     /// Set networking mode
     /// Set networking mode
     void SetNetworkMode(NetworkMode mode);
     void SetNetworkMode(NetworkMode mode);
     /// Set active flag. Only active scenes will be updated automatically
     /// Set active flag. Only active scenes will be updated automatically
     void SetActive(bool enable);
     void SetActive(bool enable);
+    /// Clear scene completely of nodes and components
+    void Clear();
+    /// Clear scene of all non-local child nodes. Note: if they have local children, they will be removed as well
+    void ClearNonLocal();
+    /// Add a required package file for multiplayer. To be called on the server
+    void AddRequiredPackageFile(PackageFile* file);
+    /// Clear required package files
+    void ClearRequiredPackageFiles();
+    /// Reset specific owner reference from nodes on disconnect
+    void ResetOwner(Connection* owner);
     
     
     /// Return node from the whole scene by ID, or null if not found
     /// Return node from the whole scene by ID, or null if not found
     Node* GetNodeByID(unsigned id) const;
     Node* GetNodeByID(unsigned id) const;
@@ -111,9 +122,19 @@ public:
     bool IsAsyncLoading() const { return asyncLoading_; }
     bool IsAsyncLoading() const { return asyncLoading_; }
     /// Return asynchronous loading progress between 0.0 and 1.0, or 1.0 if not in progress
     /// Return asynchronous loading progress between 0.0 and 1.0, or 1.0 if not in progress
     float GetAsyncProgress() const;
     float GetAsyncProgress() const;
+    /// Return source file name
+    const std::string& GetFileName() const { return fileName_; }
+    /// Return source file checksum
+    unsigned GetChecksum() const { return checksum_; }
+    /// Return required package files
+    const std::vector<SharedPtr<PackageFile> >& GetRequiredPackageFiles() const { return requiredPackageFiles_; }
+    /// Return all nodes
+    const std::map<unsigned, Node*>& GetAllNodes() const { return allNodes_; }
+    /// Return all components
+    const std::map<unsigned, Component*>& GetAllComponents() const { return allComponents_; }
     
     
     /// Get free node ID, either non-local or local
     /// Get free node ID, either non-local or local
-    unsigned GetFreeNodeID(bool local);
+    unsigned GetFreeunsigned(bool local);
     /// Get free component ID, either non-local or local
     /// Get free component ID, either non-local or local
     unsigned GetFreeComponentID(bool local);
     unsigned GetFreeComponentID(bool local);
     /// Node added. Assign scene pointer and add to ID map
     /// Node added. Assign scene pointer and add to ID map
@@ -132,6 +153,8 @@ private:
     void UpdateAsyncLoading();
     void UpdateAsyncLoading();
     /// Finish asynchronous loading
     /// Finish asynchronous loading
     void FinishAsyncLoading();
     void FinishAsyncLoading();
+    /// Finish loading
+    void FinishLoading(Deserializer* source);
     
     
     /// Map of scene nodes by ID
     /// Map of scene nodes by ID
     std::map<unsigned, Node*> allNodes_;
     std::map<unsigned, Node*> allNodes_;
@@ -141,6 +164,10 @@ private:
     NetworkMode networkMode_;
     NetworkMode networkMode_;
     /// Asynchronous loading progress
     /// Asynchronous loading progress
     AsyncProgress asyncProgress_;
     AsyncProgress asyncProgress_;
+    /// Source file name
+    std::string fileName_;
+    /// Required package files for multiplayer
+    std::vector<SharedPtr<PackageFile> > requiredPackageFiles_;
     /// Next free non-local node ID
     /// Next free non-local node ID
     unsigned nonLocalNodeID_;
     unsigned nonLocalNodeID_;
     /// Next free local node ID
     /// Next free local node ID
@@ -149,6 +176,8 @@ private:
     unsigned nonLocalComponentID_;
     unsigned nonLocalComponentID_;
     /// Next free local component ID
     /// Next free local component ID
     unsigned localComponentID_;
     unsigned localComponentID_;
+    /// Scene source file checksum
+    unsigned checksum_;
     /// Active flag
     /// Active flag
     bool active_;
     bool active_;
     /// Asynchronous loading flag
     /// Asynchronous loading flag

+ 9 - 9
Engine/Script/Addons.cpp

@@ -655,24 +655,24 @@ std::string StringSubstring2Params(unsigned start, unsigned length, const std::s
 
 
 std::string StringTrim(const std::string& str)
 std::string StringTrim(const std::string& str)
 {
 {
-    unsigned tristart_ = 0;
-    unsigned triend_ = str.length();
-    while (tristart_ < triend_)
+    unsigned trimStart = 0;
+    unsigned trimEnd = str.length();
+    while (trimStart < trimEnd)
     {
     {
-        char c = str[tristart_];
+        char c = str[trimStart];
         if ((c != ' ') && (c != 9))
         if ((c != ' ') && (c != 9))
             break;
             break;
-        ++tristart_;
+        ++trimStart;
     }
     }
-    while (triend_ > tristart_)
+    while (trimEnd > trimStart)
     {
     {
-        char c = str[triend_ - 1];
+        char c = str[trimEnd - 1];
         if ((c != ' ') && (c != 9))
         if ((c != ' ') && (c != 9))
             break;
             break;
-        --triend_;
+        --trimEnd;
     }
     }
     
     
-    return str.substr(tristart_, triend_ - tristart_);
+    return str.substr(trimStart, trimEnd - trimStart);
 }
 }
 
 
 static void ConstructStringInt(int value, std::string* ptr)
 static void ConstructStringInt(int value, std::string* ptr)