Browse Source

AS and LUA integration started, AS and LUA samples added, minor code refactoring

Arnis Lielturks 7 năm trước cách đây
mục cha
commit
8b1248c58f

+ 1 - 1
Source/CMakeLists.txt

@@ -1,5 +1,5 @@
 #
-# Copyright (c) 2008-2018 the Urho3Dproject.
+# Copyright (c) 2008-2018 the Urho3D project.
 #
 # Permission is hereby granted, free of charge, to any person obtaining a copy
 # of this software and associated documentation files (the "Software"), to deal

+ 0 - 2
Source/Samples/54_P2PMultiplayer/P2PMultiplayer.h

@@ -38,8 +38,6 @@ class HttpRequest;
 
 }
 
-const int SERVER_PORT = 54654;
-
 enum GameState {
     IN_MENU,
     IN_LOBBY,

+ 1 - 1
Source/ThirdParty/SLikeNet/Source/src/FullyConnectedMesh2.cpp

@@ -1113,7 +1113,7 @@ PluginReceiveResult FullyConnectedMesh2::OnVerifiedJoinCapable(Packet *packet)
 	DecomposeJoinCapable(packet, &vjip);
 
 	// If this assert hits, AddParticipant() was called on this system, or another system, which it should not have been.
-	//RakAssert(HasParticipant(packet->guid)==false);
+	RakAssert(HasParticipant(packet->guid)==false);
 
 	DataStructures::List<RakNetGUID> participatingMembersOnClientSucceeded;
 	DataStructures::List<RakNetGUID> participatingMembersOnClientFailed;

+ 18 - 0
Source/Urho3D/AngelScript/NetworkAPI.cpp

@@ -167,10 +167,27 @@ static HttpRequest* NetworkMakeHttpRequest(const String& url, const String& verb
 
 void RegisterNetwork(asIScriptEngine* engine)
 {
+    engine->RegisterEnum("NetworkMode");
+    engine->RegisterEnumValue("NetworkMode", "SERVER_CLIENT", SERVER_CLIENT);
+    engine->RegisterEnumValue("NetworkMode", "PEER_TO_PEER", PEER_TO_PEER);
+
     RegisterObject<Network>(engine, "Network");
     engine->RegisterObjectMethod("Network", "bool Connect(const String&in, uint16, Scene@+, const VariantMap&in identity = VariantMap())", asMETHOD(Network, Connect), asCALL_THISCALL);
     engine->RegisterObjectMethod("Network", "void Disconnect(int waitMSec = 0)", asMETHOD(Network, Disconnect), asCALL_THISCALL);
     engine->RegisterObjectMethod("Network", "bool StartServer(uint16)", asMETHOD(Network, StartServer), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Network", "bool StartSession(Scene@+, const VariantMap&in identity = VariantMap())", asMETHOD(Network, StartSession), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Network", "void JoinSession(const String&guid, Scene@+, const VariantMap&in identity = VariantMap())", asMETHOD(Network, JoinSession), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Network", "int get_participantCount()", asMETHOD(Network, GetParticipantCount), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Network", "bool IsHostSystem()", asMETHOD(Network, IsHostSystem), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Network", "const String& get_hostAddress() const", asMETHOD(Network, GetHostAddress), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Network", "void SetReady(bool)", asMETHOD(Network, SetReady), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Network", "bool GetReady(bool)", asMETHOD(Network, GetReady), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Network", "void ResetHost(bool)", asMETHOD(Network, ResetHost), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Network", "void SetNATAutoReconnect(bool)", asMETHOD(Network, SetNATAutoReconnect), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Network", "bool get_natAutoRecconect() const", asMETHOD(Network, GetNATAutoReconnect), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Network", "void SetMode(NetworkMode, bool = false)", asMETHOD(Network, SetMode), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Network", "NetworkMode GetMode()", asMETHOD(Network, GetMode), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Network", "void SetAllowedConnections(uint16)", asMETHOD(Network, SetAllowedConnections), asCALL_THISCALL);
     engine->RegisterObjectMethod("Network", "bool DiscoverHosts(uint16)", asMETHOD(Network, DiscoverHosts), asCALL_THISCALL);
 	engine->RegisterObjectMethod("Network", "bool SetDiscoveryBeacon(const VariantMap&in data = VariantMap())", asMETHOD(Network, SetDiscoveryBeacon), asCALL_THISCALL);
     engine->RegisterObjectMethod("Network", "bool SetPassword(const String&password)", asMETHOD(Network, SetPassword), asCALL_THISCALL);
@@ -202,6 +219,7 @@ void RegisterNetwork(asIScriptEngine* engine)
     engine->RegisterObjectMethod("Network", "Connection@+ get_serverConnection() const", asMETHOD(Network, GetServerConnection), asCALL_THISCALL);
     engine->RegisterObjectMethod("Network", "Array<Connection@>@ get_clientConnections() const", asFUNCTION(NetworkGetClientConnections), asCALL_CDECL_OBJLAST);
     engine->RegisterGlobalFunction("Network@+ get_network()", asFUNCTION(GetNetwork), asCALL_CDECL);
+
 }
 
 void RegisterNetworkAPI(asIScriptEngine* engine)

+ 22 - 1
Source/Urho3D/LuaScript/pkgs/Network/Network.pkg

@@ -1,5 +1,10 @@
 $#include "Network/Network.h"
 
+enum NetworkMode {
+    PEER_TO_PEER,
+    SERVER_CLIENT
+};
+
 class Network
 {
     bool Connect(const String address, unsigned short port, Scene* scene, const VariantMap& identity = Variant::emptyVariantMap);
@@ -7,7 +12,23 @@ class Network
     void Disconnect(int waitMSec = 0);
     bool StartServer(unsigned short port);
     void StopServer();
-    
+
+    bool StartSession(Scene* scene, const VariantMap& identity = Variant::emptyVariantMap);
+    void JoinSession(const String& guid, Scene* scene, const VariantMap& identity = Variant::emptyVariantMap);
+    int GetParticipantCount();
+    bool IsConnectedHost();
+    bool IsHostSystem();
+    const String GetHostAddress();
+    void SetReady(bool value);
+    bool GetReady();
+    void ReadyStatusChanged();
+    void ResetHost();
+    void SetNATAutoReconnect(bool retry);
+    const bool GetNATAutoReconnect() const { return natAutoReconnect_; }
+    void SetMode(NetworkMode mode, bool force = false);
+    const NetworkMode  GetMode() const;
+    void SetAllowedConnections(unsigned short connectionCount);
+
     void BroadcastMessage(int msgID, bool reliable, bool inOrder, const VectorBuffer& msg, unsigned contentID = 0);
 
     void BroadcastRemoteEvent(StringHash eventType, bool inOrder, const VariantMap& eventData = Variant::emptyVariantMap);

+ 1 - 12
Source/Urho3D/Network/Connection.cpp

@@ -197,19 +197,8 @@ void Connection::SetScene(Scene* newScene)
     }
     else
     {
-        // Make sure there is no existing async loading
         scene_->StopAsyncLoading();
-//        if (scene_->IsAsyncLoading()) {
-            SubscribeToEvent(scene_, E_ASYNCLOADFINISHED, URHO3D_HANDLER(Connection, HandleAsyncLoadFinished));
-//        } else {
-//            sceneLoaded_ = true;
-//            // Clear all replicated nodes
-//            scene_->Clear(true, false);
-//
-//            msg_.Clear();
-//            msg_.WriteUInt(scene_->GetChecksum());
-//            SendMessage(MSG_SCENELOADED, true, true, msg_);
-//        }
+        SubscribeToEvent(scene_, E_ASYNCLOADFINISHED, URHO3D_HANDLER(Connection, HandleAsyncLoadFinished));
     }
 }
 

+ 9 - 52
Source/Urho3D/Network/Network.cpp

@@ -367,30 +367,6 @@ void Network::HandleMessage(const SLNet::AddressOrGUID& source, int packetID, in
     }
 }
 
-void Network::HandleMessageClient(const SLNet::AddressOrGUID& source, int packetID, int msgID, const char* data, size_t numBytes)
-{
-    // Only process messages from known sources
-    Connection* connection = GetClientConnection(source);
-    if (connection)
-    {
-        MemoryBuffer msg(data, (unsigned)numBytes);
-        if (connection->ProcessMessage((int)msgID, msg))
-            return;
-
-        // If message was not handled internally, forward as an event
-        using namespace NetworkMessage;
-
-        VariantMap& eventData = GetEventDataMap();
-        eventData[P_CONNECTION] = connection;
-        eventData[P_MESSAGEID] = (int)msgID;
-        eventData[P_DATA].SetBuffer(msg.GetData(), msg.GetSize());
-        connection->SendEvent(E_NETWORKMESSAGE, eventData);
-    }
-    else {
-        URHO3D_LOGWARNING("Discarding message from unknown MessageConnection " + String(source.ToString()) + " => " + source.rakNetGuid.ToString());
-    }
-}
-
 void Network::NewConnectionEstablished(const SLNet::AddressOrGUID& connection, const char* address)
 {
     if (allowedConnectionCount_ == 0 || GetClientConnections().Size() >= allowedConnectionCount_) {
@@ -400,6 +376,8 @@ void Network::NewConnectionEstablished(const SLNet::AddressOrGUID& connection, c
     }
 
     ReadyStatusChanged();
+
+    // It is possible that in P2P mode the incomming connection is already registered
     if (networkMode_ == PEER_TO_PEER && clientConnections_[connection]) {
         //TODO proper scene state management
         clientConnections_[connection]->SetSceneLoaded(true);
@@ -410,10 +388,13 @@ void Network::NewConnectionEstablished(const SLNet::AddressOrGUID& connection, c
     // Create a new client connection corresponding to this MessageConnection
     SharedPtr<Connection> newConnection(new Connection(context_, true, connection, rakPeer_));
     newConnection->ConfigureNetworkSimulator(simulatedLatency_, simulatedPacketLoss_);
+
+    // All peers should share the same scene
     if (networkMode_ == PEER_TO_PEER && serverConnection_) {
         newConnection->SetScene(serverConnection_->GetScene());
         newConnection->SetSceneLoaded(true);
     }
+
     newConnection->SetIP(address);
     clientConnections_[connection] = newConnection;
     URHO3D_LOGINFO("Client " + newConnection->ToString() + " connected");
@@ -433,7 +414,7 @@ void Network::NewConnectionEstablished(const SLNet::AddressOrGUID& connection, c
 void Network::SendOutIdentityToPeers()
 {
     if (serverConnection_) {
-        URHO3D_LOGINFO("Sending out identity to all peers");
+        // Send our identity to all connected peers
         auto clients = GetClientConnections();
         for (auto it = clients.Begin(); it != clients.End(); ++it) {
             VectorBuffer msg;
@@ -968,7 +949,7 @@ void Network::HandleIncomingPacket(SLNet::Packet* packet, bool isServer)
         if(natPunchServerAddress_ && packet->systemAddress == *natPunchServerAddress_) {
             URHO3D_LOGINFO("Succesfully connected to NAT punchtrough server! ");
             SendEvent(E_NATMASTERCONNECTIONSUCCEEDED);
-            if (!isServer && remoteGUID_&& networkMode_ == SERVER_CLIENT)
+            if (!isServer && remoteGUID_ && networkMode_ == SERVER_CLIENT)
             {
                 natPunchthroughClient_->OpenNAT(*remoteGUID_, *natPunchServerAddress_);
             }
@@ -1082,27 +1063,7 @@ void Network::HandleIncomingPacket(SLNet::Packet* packet, bool isServer)
     {
         packetHandled = true;
     }
-    else if (packetID == ID_READY_EVENT_SET)
-    {
-        ReadyStatusChanged();
-        packetHandled = true;
-    }
-    else if (packetID == ID_READY_EVENT_UNSET)
-    {
-        ReadyStatusChanged();
-        packetHandled = true;
-    }
-    else if (packetID == ID_READY_EVENT_ALL_SET)
-    {
-        ReadyStatusChanged();
-        packetHandled = true;
-    }
-    else if (packetID == ID_READY_EVENT_QUERY)
-    {
-        ReadyStatusChanged();
-        packetHandled = true;
-    }
-    else if (packetID == ID_READY_EVENT_FORCE_ALL_SET)
+    else if (packetID == ID_READY_EVENT_SET || packetID == ID_READY_EVENT_UNSET || packetID == ID_READY_EVENT_ALL_SET || packetID == ID_READY_EVENT_QUERY || packetID == ID_READY_EVENT_FORCE_ALL_SET)
     {
         ReadyStatusChanged();
         packetHandled = true;
@@ -1279,11 +1240,7 @@ void Network::HandleIncomingPacket(SLNet::Packet* packet, bool isServer)
             MemoryBuffer buffer(packet->data + dataStart, packet->length - dataStart);
             if (serverConnection_ && !serverConnection_->ProcessMessage(packetID, buffer))
             {
-                if (networkMode_ == PEER_TO_PEER) {
-                    HandleMessageClient(packet->guid, 0, packetID, (const char *) (packet->data + dataStart), packet->length - dataStart);
-                } else {
-                    HandleMessage(packet->guid, 0, packetID, (const char*)(packet->data + dataStart), packet->length - dataStart);
-                }
+                HandleMessage(packet->guid, 0, packetID, (const char*)(packet->data + dataStart), packet->length - dataStart);
             }
         }
         packetHandled = true;

+ 0 - 2
Source/Urho3D/Network/Network.h

@@ -53,8 +53,6 @@ public:
 
     /// Handle an inbound message.
     void HandleMessage(const SLNet::AddressOrGUID& source, int packetID, int msgID, const char* data, size_t numBytes);
-    /// Handle an inbound message.
-    void HandleMessageClient(const SLNet::AddressOrGUID& source, int packetID, int msgID, const char* data, size_t numBytes);
     /// Handle a new client connection.
     void NewConnectionEstablished(const SLNet::AddressOrGUID& connection, const char* address = nullptr);
     /// Handle a client disconnection.

+ 249 - 0
bin/Data/LuaScripts/54_P2PMultiplayer.lua

@@ -0,0 +1,249 @@
+-- Chat example
+-- This sample demonstrates:
+--     - Starting up a network server or connecting to it
+--     - Implementing simple chat functionality with network messages
+
+require "LuaScripts/Utilities/Sample"
+
+local natServerAddress = nil
+local natServerPort = nil
+local saveNatSettingsButton = nil
+
+local startServerButton = nil
+
+local serverGuid = nil
+local connectButton = nil
+
+local logHistory = {}
+local logHistoryText = nil
+
+local guid = nil
+
+function Start()
+    -- Execute the common startup for samples
+    SampleStart()
+
+    -- Enable OS cursor
+    input.mouseVisible = true
+
+    -- Create the user interface
+    CreateUI()
+
+    -- Set the mouse mode to use in the sample
+    SampleInitMouseMode(MM_FREE)
+
+    -- Subscribe to UI and network events
+    SubscribeToEvents()
+
+    network:SetMode(PEER_TO_PEER);
+end
+
+function CreateUI()
+
+    SetLogoVisible(true) -- We need the full rendering window
+
+    local uiStyle = cache:GetResource("XMLFile", "UI/DefaultStyle.xml")
+    -- Set style to the UI root so that elements will inherit it
+    ui.root.defaultStyle = uiStyle
+
+    local font = cache:GetResource("Font", "Fonts/Anonymous Pro.ttf")
+    logHistoryText = ui.root:CreateChild("Text")
+    logHistoryText:SetFont(font, 12)
+    logHistoryText:SetPosition(20, 200);
+
+    local marginTop = 40
+    CreateLabel("1. Run NAT server somewhere, enter NAT server info and press 'Save NAT settings'", IntVector2(20, marginTop-20));
+    natServerAddress = CreateLineEdit("nat.frameskippers.com", 200, IntVector2(20, marginTop));
+    natServerPort = CreateLineEdit("30123", 100, IntVector2(240, marginTop));
+    saveNatSettingsButton = CreateButton("Save NAT settings", 160, IntVector2(360, marginTop));
+
+
+    marginTop = 120;
+    CreateLabel("2. Create server and give others your server GUID", IntVector2(20, marginTop-20));
+    guid = CreateLineEdit("Your server GUID", 200, IntVector2(20, marginTop));
+    startServerButton = CreateButton("Start session", 160, IntVector2(240, marginTop));
+
+
+    marginTop = 200;
+    CreateLabel("3. Input remote session GUID", IntVector2(20, marginTop-20));
+    serverGuid = CreateLineEdit("Remote server GUID", 200, IntVector2(20, marginTop));
+    connectButton = CreateButton("Join session", 160, IntVector2(240, marginTop));
+
+    local size = 20
+    for i = 1, size do
+        table.insert(logHistory, "")
+    end
+
+    -- No viewports or scene is defined. However, the default zone's fog color controls the fill color
+    renderer.defaultZone.fogColor = Color(0.0, 0.0, 0.1)
+end
+
+function SubscribeToEvents()
+    SubscribeToEvent("ServerConnected", "HandleServerConnected");
+    SubscribeToEvent("ServerDisconnected", "HandleServerDisconnected");
+    SubscribeToEvent("ConnectFailed", "HandleConnectFailed");
+
+    -- NAT server connection related events
+    SubscribeToEvent("NetworkNatMasterConnectionFailed", "HandleNatConnectionFailed");
+    SubscribeToEvent("NetworkNatMasterConnectionSucceeded", "HandleNatConnectionSucceeded");
+
+    -- NAT punchtrough request events
+    SubscribeToEvent("NetworkNatPunchtroughSucceeded", "HandleNatPunchtroughSucceeded");
+    SubscribeToEvent("NetworkNatPunchtroughFailed", "HandleNatPunchtroughFailed");
+
+    SubscribeToEvent("ClientConnected", "HandleClientConnected");
+    SubscribeToEvent("ClientDisconnected", "HandleClientDisconnected");
+
+    -- Subscribe to UI element events
+    SubscribeToEvent(saveNatSettingsButton, "Released", "HandleSaveNatSettings");
+    SubscribeToEvent(startServerButton, "Released", "HandleStartServer");
+    SubscribeToEvent(connectButton, "Released", "HandleConnect");
+end
+
+function CreateButton(text, width, position)
+    local font = cache:GetResource("Font", "Fonts/Anonymous Pro.ttf")
+
+    local button = ui.root:CreateChild("Button")
+    button:SetStyleAuto()
+    button:SetFixedWidth(width)
+    button:SetFixedHeight(30)
+    button:SetPosition(position.x, position.y)
+
+    local buttonText = button:CreateChild("Text")
+    buttonText:SetFont(font, 12)
+    buttonText:SetAlignment(HA_CENTER, VA_CENTER)
+    buttonText.text = text
+
+    return button
+end
+
+function CreateLabel(text, position)
+    local font = cache:GetResource("Font", "Fonts/Anonymous Pro.ttf")
+    local label = ui.root:CreateChild("Text")
+    label:SetFont(font, 12)
+    label.color = Color(0.0, 1.0, 0.0)
+    label:SetPosition(position.x, position.y)
+    label.text = text
+end
+
+function CreateLineEdit(placeholder, width, position)
+    local textEdit = ui.root:CreateChild("LineEdit")
+    textEdit:SetStyleAuto()
+    textEdit:SetFixedWidth(width)
+    textEdit:SetFixedHeight(30)
+    textEdit.text = placeholder
+    textEdit:SetPosition(position.x, position.y)
+    return textEdit
+end
+
+function ShowLogMessage(row)
+    table.remove(logHistory, 1)
+    table.insert(logHistory, row)
+
+    -- Concatenate all the rows in history
+    local allRows = ""
+    for i, r in ipairs(logHistory) do
+        allRows = allRows .. r .. "\n"
+    end
+    logHistoryText.text = allRows
+end
+
+
+function HandleLogMessage(eventType, eventData)
+    ShowChatText(eventData["Message"]:GetString())
+end
+
+function HandleSaveNatSettings(eventType, eventData)
+
+    local address = natServerAddress.text
+    local port = natServerPort.text
+    -- Save NAT server configuration
+    network:SetNATServerInfo(address, port);
+    ShowLogMessage("Saving NAT settings: " .. address .. ":" .. port);
+end
+
+function HandleServerConnected(eventType, eventData)
+
+end
+
+function HandleConnect(eventType, eventData)
+    local address = textEdit.text
+    if address == "" then
+        address = "localhost" -- Use localhost to connect if nothing else specified
+    end
+
+    -- Empty the text edit after reading the address to connect to
+    textEdit.text = ""
+
+    -- Connect to server, do not specify a client scene as we are not using scene replication, just messages.
+    -- At connect time we could also send identity parameters (such as username) in a VariantMap, but in this
+    -- case we skip it for simplicity
+    network:Connect(address, CHAT_SERVER_PORT, nil)
+
+end
+
+function HandleServerConnected(eventType, eventData)
+    ShowLogMessage("Client: Server connected!");
+end
+
+function HandleServerDisconnected(eventType, eventData)
+    ShowLogMessage("Client: Server disconnected!");
+end
+
+function HandleConnectFailed(eventType, eventData)
+    ShowLogMessage("Client: Connection failed!");
+end
+
+function HandleStartServer(eventType, eventData)
+    network:StartSession(scene_);
+    ShowLogMessage("Server: P2P Session started: ");
+
+    -- Output our assigned GUID which others will use to connect to our server
+    guid.text = network:GetGUID();
+end
+
+function HandleConnect(eventType, eventData)
+    local userData = VariantMap()
+    userData["Name"] = "Urho3D";
+
+    -- Attempt connecting to server using custom GUID, Scene = null as a second parameter and user identity is passed as third parameter
+    network:JoinSession(serverGuid.text, scene_, userData);
+    ShowLogMessage("Client: Joining P2P session: " + serverGuid.text);
+end
+
+function HandleNatConnectionFailed(eventType, eventData)
+    ShowLogMessage("Connection to NAT master server failed!");
+end
+
+function HandleNatConnectionSucceeded(eventType, eventData)
+    ShowLogMessage("Connection to NAT master server succeeded!");
+end
+
+function HandleNatPunchtroughSucceeded(eventType, eventData)
+    ShowLogMessage("NAT punchtrough succeeded!");
+end
+
+function HandleNatPunchtroughFailed(eventType, eventData)
+    ShowLogMessage("NAT punchtrough failed!");
+end
+
+function HandleClientConnected(eventType, eventData)
+    ShowLogMessage("Server: Client connected!");
+end
+
+function HandleClientDisconnected(eventType, eventData)
+    ShowLogMessage("Server: Client disconnected!");
+end
+
+-- Create XML patch instructions for screen joystick layout specific to this sample app
+function GetScreenJoystickPatchString()
+    return
+        "<patch>" ..
+        "    <add sel=\"/element/element[./attribute[@name='Name' and @value='Button2']]\">" ..
+        "        <attribute name=\"Is Visible\" value=\"false\" />" ..
+        "    </add>" ..
+        "    <add sel=\"/element/element[./attribute[@name='Name' and @value='Hat0']]\">" ..
+        "        <attribute name=\"Is Visible\" value=\"false\" />" ..
+        "    </add>" ..
+        "</patch>"
+end

+ 228 - 0
bin/Data/Scripts/54_P2PMultiplayer.as

@@ -0,0 +1,228 @@
+// This first example, maintaining tradition, prints a "Hello World" message.
+// Furthermore it shows:
+//     - Using the Sample utility functions as a base for the application
+//     - Adding a Text element to the graphical user interface
+//     - Subscribing to and handling of update events
+
+#include "Scripts/Utilities/Sample.as"
+
+LineEdit@ natServerAddress;
+LineEdit@ natServerPort;
+Button@ saveNatSettingsButton;
+
+Button@ startServerButton;
+
+LineEdit@ serverGuid;
+Button@ connectButton;
+
+Text@ logHistoryText;
+Array<String> logHistory;
+
+LineEdit@ guid;
+
+void Start()
+{
+    // Execute the common startup for samples
+    SampleStart();
+
+    // Create "Hello World" Text
+    CreateText();
+
+    // Set the mouse mode to use in the sample
+    SampleInitMouseMode(MM_FREE);
+
+    // Finally, hook-up this HelloWorld instance to handle update events
+    SubscribeToEvents();
+
+    network.SetMode(PEER_TO_PEER);
+}
+
+void CreateText()
+{
+    XMLFile@ uiStyle = cache.GetResource("XMLFile", "UI/DefaultStyle.xml");
+    // Set style to the UI root so that elements will inherit it
+    ui.root.defaultStyle = uiStyle;
+
+    // Create log element to view latest logs from the system
+    Font@ font = cache.GetResource("Font", "Fonts/Anonymous Pro.ttf");
+    logHistoryText = ui.root.CreateChild("Text");
+    logHistoryText.SetFont(font, 12);
+    logHistoryText.SetPosition(20, 200);
+
+    logHistory.Resize(20);
+
+    // Create NAT server config fields
+    int marginTop = 40;
+    CreateLabel("1. Run NAT server somewhere, enter NAT server info and press 'Save NAT settings'", IntVector2(20, marginTop-20));
+    natServerAddress = CreateLineEdit("nat.frameskippers.com", 200, IntVector2(20, marginTop));
+    natServerPort = CreateLineEdit("30123", 100, IntVector2(240, marginTop));
+    saveNatSettingsButton = CreateButton("Save NAT settings", 160, IntVector2(360, marginTop));
+
+    // Create server start button
+    marginTop = 120;
+    CreateLabel("2. Create server and give others your server GUID", IntVector2(20, marginTop-20));
+    guid = CreateLineEdit("Your server GUID", 200, IntVector2(20, marginTop));
+    startServerButton = CreateButton("Start session", 160, IntVector2(240, marginTop));
+
+    // Create client connection related fields
+    marginTop = 200;
+    CreateLabel("3. Input remote session GUID", IntVector2(20, marginTop-20));
+    serverGuid = CreateLineEdit("Remote server GUID", 200, IntVector2(20, marginTop));
+    connectButton = CreateButton("Join session", 160, IntVector2(240, marginTop));
+}
+
+void SubscribeToEvents()
+{
+    // Subscribe HandleUpdate() function for processing update events
+    SubscribeToEvent("ServerConnected", "HandleServerConnected");
+    SubscribeToEvent("ServerDisconnected", "HandleServerDisconnected");
+    SubscribeToEvent("ConnectFailed", "HandleConnectFailed");
+
+    // NAT server connection related events
+    SubscribeToEvent("NetworkNatMasterConnectionFailed", "HandleNatConnectionFailed");
+    SubscribeToEvent("NetworkNatMasterConnectionSucceeded", "HandleNatConnectionSucceeded");
+
+    // NAT punchtrough request events
+    SubscribeToEvent("NetworkNatPunchtroughSucceeded", "HandleNatPunchtroughSucceeded");
+    SubscribeToEvent("NetworkNatPunchtroughFailed", "HandleNatPunchtroughFailed");
+
+    SubscribeToEvent("ClientConnected", "HandleClientConnected");
+    SubscribeToEvent("ClientDisconnected", "HandleClientDisconnected");
+
+    SubscribeToEvent(saveNatSettingsButton, "Released", "HandleSaveNatSettings");
+    SubscribeToEvent(startServerButton, "Released", "HandleStartServer");
+    SubscribeToEvent(connectButton, "Released", "HandleConnect");
+}
+
+void CreateLabel(const String&in text, IntVector2 pos)
+{
+    // Create log element to view latest logs from the system
+    Font@ font = cache.GetResource("Font", "Fonts/Anonymous Pro.ttf");
+    Text@ label = ui.root.CreateChild("Text");
+    label.SetFont(font, 12);
+    label.color = Color(0.0f, 1.0f, 0.0f);
+    label.SetPosition(pos.x, pos.y);
+    label.text = text;
+}
+
+void ShowLogMessage(const String& row)
+{
+    logHistory.Erase(0);
+    logHistory.Push(row);
+
+    // Concatenate all the rows in history
+    String allRows;
+    for (uint i = 0; i < logHistory.length; ++i)
+    allRows += logHistory[i] + "\n";
+
+    logHistoryText.text = allRows;
+}
+
+LineEdit@ CreateLineEdit(const String&in placeholder, int width, IntVector2 pos)
+{
+    LineEdit@ textEdit = ui.root.CreateChild("LineEdit");
+    textEdit.SetStyleAuto();
+    textEdit.SetFixedWidth(width);
+    textEdit.SetFixedHeight(30);
+    textEdit.text = placeholder;
+    textEdit.SetPosition(pos.x, pos.y);
+    return textEdit;
+}
+
+Button@ CreateButton(const String&in text, int width, IntVector2 pos)
+{
+    Font@ font = cache.GetResource("Font", "Fonts/Anonymous Pro.ttf");
+
+    Button@ button = ui.root.CreateChild("Button");
+    button.SetStyleAuto();
+    button.SetFixedWidth(width);
+    button.SetFixedHeight(30);
+    button.SetPosition(pos.x, pos.y);
+
+    Text@ buttonText = button.CreateChild("Text");
+    buttonText.SetFont(font, 12);
+    buttonText.SetAlignment(HA_CENTER, VA_CENTER);
+    buttonText.text = text;
+
+    return button;
+}
+
+void HandleSaveNatSettings(StringHash eventType, VariantMap& eventData)
+{
+    // Save NAT server configuration
+    network.SetNATServerInfo(natServerAddress.text, natServerPort.text.ToInt());
+    ShowLogMessage("Saving NAT settings: " + natServerAddress.text + ":" + natServerPort.text);
+}
+
+void HandleServerConnected(StringHash eventType, VariantMap& eventData)
+{
+    ShowLogMessage("Client: Server connected!");
+}
+
+void HandleServerDisconnected(StringHash eventType, VariantMap& eventData)
+{
+    ShowLogMessage("Client: Server disconnected!");
+}
+
+void HandleConnectFailed(StringHash eventType, VariantMap& eventData)
+{
+    ShowLogMessage("Client: Connection failed!");
+}
+
+
+void HandleStartServer(StringHash eventType, VariantMap& eventData)
+{
+    network.StartSession(scene_);
+    ShowLogMessage("Server: Session started");
+
+    // Output our assigned GUID which others will use to connect to our server
+    guid.text = network.guid;
+}
+
+void HandleConnect(StringHash eventType, VariantMap& eventData)
+{
+    VariantMap userData;
+    userData["Name"] = "Urho3D";
+
+    // Attempt connecting to server using custom GUID, Scene = null as a second parameter and user identity is passed as third parameter
+    network.JoinSession(serverGuid.text, scene_, userData);
+    ShowLogMessage("Client: Attempting NAT punchtrough to guid: " + serverGuid.text);
+}
+
+void HandleNatConnectionFailed(StringHash eventType, VariantMap& eventData)
+{
+    ShowLogMessage("Connection to NAT master server failed!");
+}
+
+void HandleNatConnectionSucceeded(StringHash eventType, VariantMap& eventData)
+{
+    ShowLogMessage("Connection to NAT master server succeeded!");
+}
+
+void HandleNatPunchtroughSucceeded(StringHash eventType, VariantMap& eventData)
+{
+    ShowLogMessage("NAT punchtrough succeeded!");
+}
+
+void HandleNatPunchtroughFailed(StringHash eventType, VariantMap& eventData)
+{
+    ShowLogMessage("NAT punchtrough failed!");
+}
+
+void HandleClientConnected(StringHash eventType, VariantMap& eventData)
+{
+    ShowLogMessage("Server: Client connected!");
+}
+
+void HandleClientDisconnected(StringHash eventType, VariantMap& eventData)
+{
+    ShowLogMessage("Server: Client disconnected!");
+}
+
+// Create XML patch instructions for screen joystick layout specific to this sample app
+String patchInstructions =
+    "<patch>" +
+    "    <add sel=\"/element/element[./attribute[@name='Name' and @value='Hat0']]\">" +
+    "        <attribute name=\"Is Visible\" value=\"false\" />" +
+    "    </add>" +
+    "</patch>";