diff --git a/ServerProject-DONT-IMPORT-INTO-UNITY/LRM/Config.cs b/ServerProject-DONT-IMPORT-INTO-UNITY/LRM/Config.cs
index 145f5c1..8e5b6ae 100644
--- a/ServerProject-DONT-IMPORT-INTO-UNITY/LRM/Config.cs
+++ b/ServerProject-DONT-IMPORT-INTO-UNITY/LRM/Config.cs
@@ -12,6 +12,7 @@ namespace LightReflectiveMirror
public string TransportDLL = "MultiCompiled.dll";
public string TransportClass = "Mirror.SimpleWebTransport";
public string AuthenticationKey = "Secret Auth Key";
+ public ushort TransportPort = 7777;
public int UpdateLoopTime = 10;
public int UpdateHeartbeatInterval = 100;
diff --git a/ServerProject-DONT-IMPORT-INTO-UNITY/LRM/Program/Program.cs b/ServerProject-DONT-IMPORT-INTO-UNITY/LRM/Program/Program.cs
index a7a9152..da55ff6 100644
--- a/ServerProject-DONT-IMPORT-INTO-UNITY/LRM/Program/Program.cs
+++ b/ServerProject-DONT-IMPORT-INTO-UNITY/LRM/Program/Program.cs
@@ -104,7 +104,7 @@ namespace LightReflectiveMirror
_pendingNATPunches.Remove(clientID);
};
- transport.ServerStart();
+ transport.ServerStart(conf.TransportPort);
WriteLogMessage("OK", ConsoleColor.Green);
diff --git a/ServerProject-DONT-IMPORT-INTO-UNITY/LRM/Transport.cs b/ServerProject-DONT-IMPORT-INTO-UNITY/LRM/Transport.cs
index abcd0ec..7e259f2 100644
--- a/ServerProject-DONT-IMPORT-INTO-UNITY/LRM/Transport.cs
+++ b/ServerProject-DONT-IMPORT-INTO-UNITY/LRM/Transport.cs
@@ -159,7 +159,7 @@ namespace Mirror
///
/// Start listening for clients
///
- public abstract void ServerStart();
+ public abstract void ServerStart(ushort port);
///
/// Send data to a client.
diff --git a/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Class1.cs b/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Class1.cs
deleted file mode 100644
index 20978f2..0000000
--- a/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Class1.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-using System;
-
-namespace MultiCompiled
-{
- public class Class1
- {
- }
-}
diff --git a/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/MultiCompiled.csproj b/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/MultiCompiled.csproj
index f208d30..4b9a9ca 100644
--- a/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/MultiCompiled.csproj
+++ b/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/MultiCompiled.csproj
@@ -4,4 +4,12 @@
net5.0
+
+
+
+
+
+
+
+
diff --git a/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/Telepathy/Client.cs b/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/Telepathy/Client.cs
new file mode 100644
index 0000000..73e775c
--- /dev/null
+++ b/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/Telepathy/Client.cs
@@ -0,0 +1,362 @@
+using System;
+using System.Net.Sockets;
+using System.Threading;
+
+namespace Telepathy
+{
+ // ClientState OBJECT that can be handed to the ReceiveThread safely.
+ // => allows us to create a NEW OBJECT every time we connect and start a
+ // receive thread.
+ // => perfectly protects us against data races. fixes all the flaky tests
+ // where .Connecting or .client would still be used by a dieing thread
+ // while attempting to use it for a new connection attempt etc.
+ // => creating a fresh client state each time is the best solution against
+ // data races here!
+ class ClientConnectionState : ConnectionState
+ {
+ public Thread receiveThread;
+
+ // TcpClient.Connected doesn't check if socket != null, which
+ // results in NullReferenceExceptions if connection was closed.
+ // -> let's check it manually instead
+ public bool Connected => client != null &&
+ client.Client != null &&
+ client.Client.Connected;
+
+ // TcpClient has no 'connecting' state to check. We need to keep track
+ // of it manually.
+ // -> checking 'thread.IsAlive && !Connected' is not enough because the
+ // thread is alive and connected is false for a short moment after
+ // disconnecting, so this would cause race conditions.
+ // -> we use a threadsafe bool wrapper so that ThreadFunction can remain
+ // static (it needs a common lock)
+ // => Connecting is true from first Connect() call in here, through the
+ // thread start, until TcpClient.Connect() returns. Simple and clear.
+ // => bools are atomic according to
+ // https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/variables
+ // made volatile so the compiler does not reorder access to it
+ public volatile bool Connecting;
+
+ // thread safe pipe for received messages
+ // => inside client connection state so that we can create a new state
+ // each time we connect
+ // (unlike server which has one receive pipe for all connections)
+ public readonly MagnificentReceivePipe receivePipe;
+
+ // constructor always creates new TcpClient for client connection!
+ public ClientConnectionState(int MaxMessageSize) : base(new TcpClient(), MaxMessageSize)
+ {
+ // create receive pipe with max message size for pooling
+ receivePipe = new MagnificentReceivePipe(MaxMessageSize);
+ }
+
+ // dispose all the state safely
+ public void Dispose()
+ {
+ // close client
+ client.Close();
+
+ // wait until thread finished. this is the only way to guarantee
+ // that we can call Connect() again immediately after Disconnect
+ // -> calling .Join would sometimes wait forever, e.g. when
+ // calling Disconnect while trying to connect to a dead end
+ receiveThread?.Interrupt();
+
+ // we interrupted the receive Thread, so we can't guarantee that
+ // connecting was reset. let's do it manually.
+ Connecting = false;
+
+ // clear send pipe. no need to hold on to elements.
+ // (unlike receiveQueue, which is still needed to process the
+ // latest Disconnected message, etc.)
+ sendPipe.Clear();
+
+ // IMPORTANT: DO NOT CLEAR RECEIVE PIPE.
+ // we still want to process disconnect messages in Tick()!
+
+ // let go of this client completely. the thread ended, no one uses
+ // it anymore and this way Connected is false again immediately.
+ client = null;
+ }
+ }
+
+ public class Client : Common
+ {
+ // events to hook into
+ // => OnData uses ArraySegment for allocation free receives later
+ public Action OnConnected;
+ public Action> OnData;
+ public Action OnDisconnected;
+
+ // disconnect if send queue gets too big.
+ // -> avoids ever growing queue memory if network is slower than input
+ // -> disconnecting is great for load balancing. better to disconnect
+ // one connection than risking every connection / the whole server
+ // -> huge queue would introduce multiple seconds of latency anyway
+ //
+ // Mirror/DOTSNET use MaxMessageSize batching, so for a 16kb max size:
+ // limit = 1,000 means 16 MB of memory/connection
+ // limit = 10,000 means 160 MB of memory/connection
+ public int SendQueueLimit = 10000;
+ public int ReceiveQueueLimit = 10000;
+
+ // all client state wrapped into an object that is passed to ReceiveThread
+ // => we create a new one each time we connect to avoid data races with
+ // old dieing threads still using the previous object!
+ ClientConnectionState state;
+
+ // Connected & Connecting
+ public bool Connected => state != null && state.Connected;
+ public bool Connecting => state != null && state.Connecting;
+
+ // pipe count, useful for debugging / benchmarks
+ public int ReceivePipeCount => state != null ? state.receivePipe.TotalCount : 0;
+
+ // constructor
+ public Client(int MaxMessageSize) : base(MaxMessageSize) {}
+
+ // the thread function
+ // STATIC to avoid sharing state!
+ // => pass ClientState object. a new one is created for each new thread!
+ // => avoids data races where an old dieing thread might still modify
+ // the current thread's state :/
+ static void ReceiveThreadFunction(ClientConnectionState state, string ip, int port, int MaxMessageSize, bool NoDelay, int SendTimeout, int ReceiveTimeout, int ReceiveQueueLimit)
+
+ {
+ Thread sendThread = null;
+
+ // absolutely must wrap with try/catch, otherwise thread
+ // exceptions are silent
+ try
+ {
+ // connect (blocking)
+ state.client.Connect(ip, port);
+ state.Connecting = false; // volatile!
+
+ // set socket options after the socket was created in Connect()
+ // (not after the constructor because we clear the socket there)
+ state.client.NoDelay = NoDelay;
+ state.client.SendTimeout = SendTimeout;
+ state.client.ReceiveTimeout = ReceiveTimeout;
+
+ // start send thread only after connected
+ // IMPORTANT: DO NOT SHARE STATE ACROSS MULTIPLE THREADS!
+ sendThread = new Thread(() => { ThreadFunctions.SendLoop(0, state.client, state.sendPipe, state.sendPending); });
+ sendThread.IsBackground = true;
+ sendThread.Start();
+
+ // run the receive loop
+ // (receive pipe is shared across all loops)
+ ThreadFunctions.ReceiveLoop(0, state.client, MaxMessageSize, state.receivePipe, ReceiveQueueLimit);
+ }
+ catch (SocketException exception)
+ {
+ // this happens if (for example) the ip address is correct
+ // but there is no server running on that ip/port
+ Log.Info("Client Recv: failed to connect to ip=" + ip + " port=" + port + " reason=" + exception);
+
+ // add 'Disconnected' event to receive pipe so that the caller
+ // knows that the Connect failed. otherwise they will never know
+ state.receivePipe.Enqueue(0, EventType.Disconnected, default);
+ }
+ catch (ThreadInterruptedException)
+ {
+ // expected if Disconnect() aborts it
+ }
+ catch (ThreadAbortException)
+ {
+ // expected if Disconnect() aborts it
+ }
+ catch (ObjectDisposedException)
+ {
+ // expected if Disconnect() aborts it and disposed the client
+ // while ReceiveThread is in a blocking Connect() call
+ }
+ catch (Exception exception)
+ {
+ // something went wrong. probably important.
+ Log.Error("Client Recv Exception: " + exception);
+ }
+
+ // sendthread might be waiting on ManualResetEvent,
+ // so let's make sure to end it if the connection
+ // closed.
+ // otherwise the send thread would only end if it's
+ // actually sending data while the connection is
+ // closed.
+ sendThread?.Interrupt();
+
+ // Connect might have failed. thread might have been closed.
+ // let's reset connecting state no matter what.
+ state.Connecting = false;
+
+ // if we got here then we are done. ReceiveLoop cleans up already,
+ // but we may never get there if connect fails. so let's clean up
+ // here too.
+ state.client?.Close();
+ }
+
+ public void Connect(string ip, int port)
+ {
+ // not if already started
+ if (Connecting || Connected)
+ {
+ Log.Warning("Telepathy Client can not create connection because an existing connection is connecting or connected");
+ return;
+ }
+
+ // overwrite old thread's state object. create a new one to avoid
+ // data races where an old dieing thread might still modify the
+ // current state! fixes all the flaky tests!
+ state = new ClientConnectionState(MaxMessageSize);
+
+ // We are connecting from now until Connect succeeds or fails
+ state.Connecting = true;
+
+ // create a TcpClient with perfect IPv4, IPv6 and hostname resolving
+ // support.
+ //
+ // * TcpClient(hostname, port): works but would connect (and block)
+ // already
+ // * TcpClient(AddressFamily.InterNetworkV6): takes Ipv4 and IPv6
+ // addresses but only connects to IPv6 servers (e.g. Telepathy).
+ // does NOT connect to IPv4 servers (e.g. Mirror Booster), even
+ // with DualMode enabled.
+ // * TcpClient(): creates IPv4 socket internally, which would force
+ // Connect() to only use IPv4 sockets.
+ //
+ // => the trick is to clear the internal IPv4 socket so that Connect
+ // resolves the hostname and creates either an IPv4 or an IPv6
+ // socket as needed (see TcpClient source)
+ state.client.Client = null; // clear internal IPv4 socket until Connect()
+
+ // client.Connect(ip, port) is blocking. let's call it in the thread
+ // and return immediately.
+ // -> this way the application doesn't hang for 30s if connect takes
+ // too long, which is especially good in games
+ // -> this way we don't async client.BeginConnect, which seems to
+ // fail sometimes if we connect too many clients too fast
+ state.receiveThread = new Thread(() => {
+ ReceiveThreadFunction(state, ip, port, MaxMessageSize, NoDelay, SendTimeout, ReceiveTimeout, ReceiveQueueLimit);
+ });
+ state.receiveThread.IsBackground = true;
+ state.receiveThread.Start();
+ }
+
+ public void Disconnect()
+ {
+ // only if started
+ if (Connecting || Connected)
+ {
+ // dispose all the state safely
+ state.Dispose();
+
+ // IMPORTANT: DO NOT set state = null!
+ // we still want to process the pipe's disconnect message etc.!
+ }
+ }
+
+ // send message to server using socket connection.
+ // arraysegment for allocation free sends later.
+ // -> the segment's array is only used until Send() returns!
+ public bool Send(ArraySegment message)
+ {
+ if (Connected)
+ {
+ // respect max message size to avoid allocation attacks.
+ if (message.Count <= MaxMessageSize)
+ {
+ // check send pipe limit
+ if (state.sendPipe.Count < SendQueueLimit)
+ {
+ // add to thread safe send pipe and return immediately.
+ // calling Send here would be blocking (sometimes for long
+ // times if other side lags or wire was disconnected)
+ state.sendPipe.Enqueue(message);
+ state.sendPending.Set(); // interrupt SendThread WaitOne()
+ return true;
+ }
+ // disconnect if send queue gets too big.
+ // -> avoids ever growing queue memory if network is slower
+ // than input
+ // -> avoids ever growing latency as well
+ //
+ // note: while SendThread always grabs the WHOLE send queue
+ // immediately, it's still possible that the sending
+ // blocks for so long that the send queue just gets
+ // way too big. have a limit - better safe than sorry.
+ else
+ {
+ // log the reason
+ Log.Warning($"Client.Send: sendPipe reached limit of {SendQueueLimit}. This can happen if we call send faster than the network can process messages. Disconnecting to avoid ever growing memory & latency.");
+
+ // just close it. send thread will take care of the rest.
+ state.client.Close();
+ return false;
+ }
+ }
+ Log.Error("Client.Send: message too big: " + message.Count + ". Limit: " + MaxMessageSize);
+ return false;
+ }
+ Log.Warning("Client.Send: not connected!");
+ return false;
+ }
+
+ // tick: processes up to 'limit' messages
+ // => limit parameter to avoid deadlocks / too long freezes if server or
+ // client is too slow to process network load
+ // => Mirror & DOTSNET need to have a process limit anyway.
+ // might as well do it here and make life easier.
+ // => returns amount of remaining messages to process, so the caller
+ // can call tick again as many times as needed (or up to a limit)
+ //
+ // Tick() may process multiple messages, but Mirror needs a way to stop
+ // processing immediately if a scene change messages arrives. Mirror
+ // can't process any other messages during a scene change.
+ // (could be useful for others too)
+ // => make sure to allocate the lambda only once in transports
+ public int Tick(int processLimit, Func checkEnabled = null)
+ {
+ // only if state was created yet (after connect())
+ // note: we don't check 'only if connected' because we want to still
+ // process Disconnect messages afterwards too!
+ if (state == null)
+ return 0;
+
+ // process up to 'processLimit' messages
+ for (int i = 0; i < processLimit; ++i)
+ {
+ // check enabled in case a Mirror scene message arrived
+ if (checkEnabled != null && !checkEnabled())
+ break;
+
+ // peek first. allows us to process the first queued entry while
+ // still keeping the pooled byte[] alive by not removing anything.
+ if (state.receivePipe.TryPeek(out int _, out EventType eventType, out ArraySegment message))
+ {
+ switch (eventType)
+ {
+ case EventType.Connected:
+ OnConnected?.Invoke();
+ break;
+ case EventType.Data:
+ OnData?.Invoke(message);
+ break;
+ case EventType.Disconnected:
+ OnDisconnected?.Invoke();
+ break;
+ }
+
+ // IMPORTANT: now dequeue and return it to pool AFTER we are
+ // done processing the event.
+ state.receivePipe.TryDequeue();
+ }
+ // no more messages. stop the loop.
+ else break;
+ }
+
+ // return what's left to process for next time
+ return state.receivePipe.TotalCount;
+ }
+ }
+}
diff --git a/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/Telepathy/Common.cs b/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/Telepathy/Common.cs
new file mode 100644
index 0000000..15265f9
--- /dev/null
+++ b/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/Telepathy/Common.cs
@@ -0,0 +1,39 @@
+// common code used by server and client
+namespace Telepathy
+{
+ public abstract class Common
+ {
+ // IMPORTANT: DO NOT SHARE STATE ACROSS SEND/RECV LOOPS (DATA RACES)
+ // (except receive pipe which is used for all threads)
+
+ // NoDelay disables nagle algorithm. lowers CPU% and latency but
+ // increases bandwidth
+ public bool NoDelay = true;
+
+ // Prevent allocation attacks. Each packet is prefixed with a length
+ // header, so an attacker could send a fake packet with length=2GB,
+ // causing the server to allocate 2GB and run out of memory quickly.
+ // -> simply increase max packet size if you want to send around bigger
+ // files!
+ // -> 16KB per message should be more than enough.
+ public readonly int MaxMessageSize;
+
+ // Send would stall forever if the network is cut off during a send, so
+ // we need a timeout (in milliseconds)
+ public int SendTimeout = 5000;
+
+ // Default TCP receive time out can be huge (minutes).
+ // That's way too much for games, let's make it configurable.
+ // we need a timeout (in milliseconds)
+ // => '0' means disabled
+ // => disabled by default because some people might use Telepathy
+ // without Mirror and without sending pings, so timeouts are likely
+ public int ReceiveTimeout = 0;
+
+ // constructor
+ protected Common(int MaxMessageSize)
+ {
+ this.MaxMessageSize = MaxMessageSize;
+ }
+ }
+}
diff --git a/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/Telepathy/ConnectionState.cs b/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/Telepathy/ConnectionState.cs
new file mode 100644
index 0000000..cdfe3c0
--- /dev/null
+++ b/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/Telepathy/ConnectionState.cs
@@ -0,0 +1,35 @@
+// both server and client need a connection state object.
+// -> server needs it to keep track of multiple connections
+// -> client needs it to safely create a new connection state on every new
+// connect in order to avoid data races where a dieing thread might still
+// modify the current state. can't happen if we create a new state each time!
+// (fixes all the flaky tests)
+//
+// ... besides, it also allows us to share code!
+using System.Net.Sockets;
+using System.Threading;
+
+namespace Telepathy
+{
+ public class ConnectionState
+ {
+ public TcpClient client;
+
+ // thread safe pipe to send messages from main thread to send thread
+ public readonly MagnificentSendPipe sendPipe;
+
+ // ManualResetEvent to wake up the send thread. better than Thread.Sleep
+ // -> call Set() if everything was sent
+ // -> call Reset() if there is something to send again
+ // -> call WaitOne() to block until Reset was called
+ public ManualResetEvent sendPending = new ManualResetEvent(false);
+
+ public ConnectionState(TcpClient client, int MaxMessageSize)
+ {
+ this.client = client;
+
+ // create send pipe with max message size for pooling
+ sendPipe = new MagnificentSendPipe(MaxMessageSize);
+ }
+ }
+}
\ No newline at end of file
diff --git a/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/Telepathy/EventType.cs b/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/Telepathy/EventType.cs
new file mode 100644
index 0000000..66bc3b4
--- /dev/null
+++ b/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/Telepathy/EventType.cs
@@ -0,0 +1,9 @@
+namespace Telepathy
+{
+ public enum EventType
+ {
+ Connected,
+ Data,
+ Disconnected
+ }
+}
diff --git a/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/Telepathy/LICENSE b/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/Telepathy/LICENSE
new file mode 100644
index 0000000..680deef
--- /dev/null
+++ b/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/Telepathy/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2018, vis2k
+
+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.
diff --git a/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/Telepathy/Log.cs b/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/Telepathy/Log.cs
new file mode 100644
index 0000000..2d50aa3
--- /dev/null
+++ b/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/Telepathy/Log.cs
@@ -0,0 +1,15 @@
+// A simple logger class that uses Console.WriteLine by default.
+// Can also do Logger.LogMethod = Debug.Log for Unity etc.
+// (this way we don't have to depend on UnityEngine.DLL and don't need a
+// different version for every UnityEngine version here)
+using System;
+
+namespace Telepathy
+{
+ public static class Log
+ {
+ public static Action Info = Console.WriteLine;
+ public static Action Warning = Console.WriteLine;
+ public static Action Error = Console.Error.WriteLine;
+ }
+}
diff --git a/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/Telepathy/Logger.cs b/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/Telepathy/Logger.cs
new file mode 100644
index 0000000..4f7722a
--- /dev/null
+++ b/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/Telepathy/Logger.cs
@@ -0,0 +1 @@
+// removed 2021-02-04
\ No newline at end of file
diff --git a/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/Telepathy/MagnificentReceivePipe.cs b/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/Telepathy/MagnificentReceivePipe.cs
new file mode 100644
index 0000000..2e10318
--- /dev/null
+++ b/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/Telepathy/MagnificentReceivePipe.cs
@@ -0,0 +1,222 @@
+// a magnificent receive pipe to shield us from all of life's complexities.
+// safely sends messages from receive thread to main thread.
+// -> thread safety built in
+// -> byte[] pooling coming in the future
+//
+// => hides all the complexity from telepathy
+// => easy to switch between stack/queue/concurrentqueue/etc.
+// => easy to test
+using System;
+using System.Collections.Generic;
+
+namespace Telepathy
+{
+ public class MagnificentReceivePipe
+ {
+ // queue entry message. only used in here.
+ // -> byte arrays are always of 4 + MaxMessageSize
+ // -> ArraySegment indicates the actual message content
+ struct Entry
+ {
+ public int connectionId;
+ public EventType eventType;
+ public ArraySegment data;
+ public Entry(int connectionId, EventType eventType, ArraySegment data)
+ {
+ this.connectionId = connectionId;
+ this.eventType = eventType;
+ this.data = data;
+ }
+ }
+
+ // message queue
+ // ConcurrentQueue allocates. lock{} instead.
+ //
+ // IMPORTANT: lock{} all usages!
+ readonly Queue queue = new Queue();
+
+ // byte[] pool to avoid allocations
+ // Take & Return is beautifully encapsulated in the pipe.
+ // the outside does not need to worry about anything.
+ // and it can be tested easily.
+ //
+ // IMPORTANT: lock{} all usages!
+ Pool pool;
+
+ // unfortunately having one receive pipe per connetionId is way slower
+ // in CCU tests. right now we have one pipe for all connections.
+ // => we still need to limit queued messages per connection to avoid one
+ // spamming connection being able to slow down everyone else since
+ // the queue would be full of just this connection's messages forever
+ // => let's use a simpler per-connectionId counter for now
+ Dictionary queueCounter = new Dictionary();
+
+ // constructor
+ public MagnificentReceivePipe(int MaxMessageSize)
+ {
+ // initialize pool to create max message sized byte[]s each time
+ pool = new Pool(() => new byte[MaxMessageSize]);
+ }
+
+ // return amount of queued messages for this connectionId.
+ // for statistics. don't call Count and assume that it's the same after
+ // the call.
+ public int Count(int connectionId)
+ {
+ lock (this)
+ {
+ return queueCounter.TryGetValue(connectionId, out int count)
+ ? count
+ : 0;
+ }
+ }
+
+ // total count
+ public int TotalCount
+ {
+ get { lock (this) { return queue.Count; } }
+ }
+
+ // pool count for testing
+ public int PoolCount
+ {
+ get { lock (this) { return pool.Count(); } }
+ }
+
+ // enqueue a message
+ // -> ArraySegment to avoid allocations later
+ // -> parameters passed directly so it's more obvious that we don't just
+ // queue a passed 'Message', instead we copy the ArraySegment into
+ // a byte[] and store it internally, etc.)
+ public void Enqueue(int connectionId, EventType eventType, ArraySegment message)
+ {
+ // pool & queue usage always needs to be locked
+ lock (this)
+ {
+ // does this message have a data array content?
+ ArraySegment segment = default;
+ if (message != default)
+ {
+ // ArraySegment is only valid until returning.
+ // copy it into a byte[] that we can store.
+ // ArraySegment array is only valid until returning, so copy
+ // it into a byte[] that we can queue safely.
+
+ // get one from the pool first to avoid allocations
+ byte[] bytes = pool.Take();
+
+ // copy into it
+ Buffer.BlockCopy(message.Array, message.Offset, bytes, 0, message.Count);
+
+ // indicate which part is the message
+ segment = new ArraySegment(bytes, 0, message.Count);
+ }
+
+ // enqueue it
+ // IMPORTANT: pass the segment around pool byte[],
+ // NOT the 'message' that is only valid until returning!
+ Entry entry = new Entry(connectionId, eventType, segment);
+ queue.Enqueue(entry);
+
+ // increase counter for this connectionId
+ int oldCount = Count(connectionId);
+ queueCounter[connectionId] = oldCount + 1;
+ }
+ }
+
+ // peek the next message
+ // -> allows the caller to process it while pipe still holds on to the
+ // byte[]
+ // -> TryDequeue should be called after processing, so that the message
+ // is actually dequeued and the byte[] is returned to pool!
+ // => see TryDequeue comments!
+ //
+ // IMPORTANT: TryPeek & Dequeue need to be called from the SAME THREAD!
+ public bool TryPeek(out int connectionId, out EventType eventType, out ArraySegment data)
+ {
+ connectionId = 0;
+ eventType = EventType.Disconnected;
+ data = default;
+
+ // pool & queue usage always needs to be locked
+ lock (this)
+ {
+ if (queue.Count > 0)
+ {
+ Entry entry = queue.Peek();
+ connectionId = entry.connectionId;
+ eventType = entry.eventType;
+ data = entry.data;
+ return true;
+ }
+ return false;
+ }
+ }
+
+ // dequeue the next message
+ // -> simply dequeues and returns the byte[] to pool (if any)
+ // -> use Peek to actually process the first element while the pipe
+ // still holds on to the byte[]
+ // -> doesn't return the element because the byte[] needs to be returned
+ // to the pool in dequeue. caller can't be allowed to work with a
+ // byte[] that is already returned to pool.
+ // => Peek & Dequeue is the most simple, clean solution for receive
+ // pipe pooling to avoid allocations!
+ //
+ // IMPORTANT: TryPeek & Dequeue need to be called from the SAME THREAD!
+ public bool TryDequeue()
+ {
+ // pool & queue usage always needs to be locked
+ lock (this)
+ {
+ if (queue.Count > 0)
+ {
+ // dequeue from queue
+ Entry entry = queue.Dequeue();
+
+ // return byte[] to pool (if any).
+ // not all message types have byte[] contents.
+ if (entry.data != default)
+ {
+ pool.Return(entry.data.Array);
+ }
+
+ // decrease counter for this connectionId
+ queueCounter[entry.connectionId]--;
+
+ // remove if zero. don't want to keep old connectionIds in
+ // there forever, it would cause slowly growing memory.
+ if (queueCounter[entry.connectionId] == 0)
+ queueCounter.Remove(entry.connectionId);
+
+ return true;
+ }
+ return false;
+ }
+ }
+
+ public void Clear()
+ {
+ // pool & queue usage always needs to be locked
+ lock (this)
+ {
+ // clear queue, but via dequeue to return each byte[] to pool
+ while (queue.Count > 0)
+ {
+ // dequeue
+ Entry entry = queue.Dequeue();
+
+ // return byte[] to pool (if any).
+ // not all message types have byte[] contents.
+ if (entry.data != default)
+ {
+ pool.Return(entry.data.Array);
+ }
+ }
+
+ // clear counter too
+ queueCounter.Clear();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/Telepathy/MagnificentSendPipe.cs b/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/Telepathy/MagnificentSendPipe.cs
new file mode 100644
index 0000000..be456a0
--- /dev/null
+++ b/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/Telepathy/MagnificentSendPipe.cs
@@ -0,0 +1,165 @@
+// a magnificent send pipe to shield us from all of life's complexities.
+// safely sends messages from main thread to send thread.
+// -> thread safety built in
+// -> byte[] pooling coming in the future
+//
+// => hides all the complexity from telepathy
+// => easy to switch between stack/queue/concurrentqueue/etc.
+// => easy to test
+
+using System;
+using System.Collections.Generic;
+
+namespace Telepathy
+{
+ public class MagnificentSendPipe
+ {
+ // message queue
+ // ConcurrentQueue allocates. lock{} instead.
+ // -> byte arrays are always of MaxMessageSize
+ // -> ArraySegment indicates the actual message content
+ //
+ // IMPORTANT: lock{} all usages!
+ readonly Queue> queue = new Queue>();
+
+ // byte[] pool to avoid allocations
+ // Take & Return is beautifully encapsulated in the pipe.
+ // the outside does not need to worry about anything.
+ // and it can be tested easily.
+ //
+ // IMPORTANT: lock{} all usages!
+ Pool pool;
+
+ // constructor
+ public MagnificentSendPipe(int MaxMessageSize)
+ {
+ // initialize pool to create max message sized byte[]s each time
+ pool = new Pool(() => new byte[MaxMessageSize]);
+ }
+
+ // for statistics. don't call Count and assume that it's the same after
+ // the call.
+ public int Count
+ {
+ get { lock (this) { return queue.Count; } }
+ }
+
+ // pool count for testing
+ public int PoolCount
+ {
+ get { lock (this) { return pool.Count(); } }
+ }
+
+ // enqueue a message
+ // arraysegment for allocation free sends later.
+ // -> the segment's array is only used until Enqueue() returns!
+ public void Enqueue(ArraySegment message)
+ {
+ // pool & queue usage always needs to be locked
+ lock (this)
+ {
+ // ArraySegment array is only valid until returning, so copy
+ // it into a byte[] that we can queue safely.
+
+ // get one from the pool first to avoid allocations
+ byte[] bytes = pool.Take();
+
+ // copy into it
+ Buffer.BlockCopy(message.Array, message.Offset, bytes, 0, message.Count);
+
+ // indicate which part is the message
+ ArraySegment segment = new ArraySegment(bytes, 0, message.Count);
+
+ // now enqueue it
+ queue.Enqueue(segment);
+ }
+ }
+
+ // send threads need to dequeue each byte[] and write it into the socket
+ // -> dequeueing one byte[] after another works, but it's WAY slower
+ // than dequeueing all immediately (locks only once)
+ // lock{} & DequeueAll is WAY faster than ConcurrentQueue & dequeue
+ // one after another:
+ //
+ // uMMORPG 450 CCU
+ // SafeQueue: 900-1440ms latency
+ // ConcurrentQueue: 2000ms latency
+ //
+ // -> the most obvious solution is to just return a list with all byte[]
+ // (which allocates) and then write each one into the socket
+ // -> a faster solution is to serialize each one into one payload buffer
+ // and pass that to the socket only once. fewer socket calls always
+ // give WAY better CPU performance(!)
+ // -> to avoid allocating a new list of entries each time, we simply
+ // serialize all entries into the payload here already
+ // => having all this complexity built into the pipe makes testing and
+ // modifying the algorithm super easy!
+ //
+ // IMPORTANT: serializing in here will allow us to return the byte[]
+ // entries back to a pool later to completely avoid
+ // allocations!
+ public bool DequeueAndSerializeAll(ref byte[] payload, out int packetSize)
+ {
+ // pool & queue usage always needs to be locked
+ lock (this)
+ {
+ // do nothing if empty
+ packetSize = 0;
+ if (queue.Count == 0)
+ return false;
+
+ // we might have multiple pending messages. merge into one
+ // packet to avoid TCP overheads and improve performance.
+ //
+ // IMPORTANT: Mirror & DOTSNET already batch into MaxMessageSize
+ // chunks, but we STILL pack all pending messages
+ // into one large payload so we only give it to TCP
+ // ONCE. This is HUGE for performance so we keep it!
+ packetSize = 0;
+ foreach (ArraySegment message in queue)
+ packetSize += 4 + message.Count; // header + content
+
+ // create payload buffer if not created yet or previous one is
+ // too small
+ // IMPORTANT: payload.Length might be > packetSize! don't use it!
+ if (payload == null || payload.Length < packetSize)
+ payload = new byte[packetSize];
+
+ // dequeue all byte[] messages and serialize into the packet
+ int position = 0;
+ while (queue.Count > 0)
+ {
+ // dequeue
+ ArraySegment message = queue.Dequeue();
+
+ // write header (size) into buffer at position
+ Utils.IntToBytesBigEndianNonAlloc(message.Count, payload, position);
+ position += 4;
+
+ // copy message into payload at position
+ Buffer.BlockCopy(message.Array, message.Offset, payload, position, message.Count);
+ position += message.Count;
+
+ // return to pool so it can be reused (avoids allocations!)
+ pool.Return(message.Array);
+ }
+
+ // we did serialize something
+ return true;
+ }
+ }
+
+ public void Clear()
+ {
+ // pool & queue usage always needs to be locked
+ lock (this)
+ {
+ // clear queue, but via dequeue to return each byte[] to pool
+ while (queue.Count > 0)
+ {
+ pool.Return(queue.Dequeue().Array);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/Telepathy/Message.cs b/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/Telepathy/Message.cs
new file mode 100644
index 0000000..4f7722a
--- /dev/null
+++ b/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/Telepathy/Message.cs
@@ -0,0 +1 @@
+// removed 2021-02-04
\ No newline at end of file
diff --git a/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/Telepathy/NetworkStreamExtensions.cs b/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/Telepathy/NetworkStreamExtensions.cs
new file mode 100644
index 0000000..bda4f9e
--- /dev/null
+++ b/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/Telepathy/NetworkStreamExtensions.cs
@@ -0,0 +1,58 @@
+using System.IO;
+using System.Net.Sockets;
+
+namespace Telepathy
+{
+ public static class NetworkStreamExtensions
+ {
+ // .Read returns '0' if remote closed the connection but throws an
+ // IOException if we voluntarily closed our own connection.
+ //
+ // let's add a ReadSafely method that returns '0' in both cases so we don't
+ // have to worry about exceptions, since a disconnect is a disconnect...
+ public static int ReadSafely(this NetworkStream stream, byte[] buffer, int offset, int size)
+ {
+ try
+ {
+ return stream.Read(buffer, offset, size);
+ }
+ catch (IOException)
+ {
+ return 0;
+ }
+ }
+
+ // helper function to read EXACTLY 'n' bytes
+ // -> default .Read reads up to 'n' bytes. this function reads exactly
+ // 'n' bytes
+ // -> this is blocking until 'n' bytes were received
+ // -> immediately returns false in case of disconnects
+ public static bool ReadExactly(this NetworkStream stream, byte[] buffer, int amount)
+ {
+ // there might not be enough bytes in the TCP buffer for .Read to read
+ // the whole amount at once, so we need to keep trying until we have all
+ // the bytes (blocking)
+ //
+ // note: this just is a faster version of reading one after another:
+ // for (int i = 0; i < amount; ++i)
+ // if (stream.Read(buffer, i, 1) == 0)
+ // return false;
+ // return true;
+ int bytesRead = 0;
+ while (bytesRead < amount)
+ {
+ // read up to 'remaining' bytes with the 'safe' read extension
+ int remaining = amount - bytesRead;
+ int result = stream.ReadSafely(buffer, bytesRead, remaining);
+
+ // .Read returns 0 if disconnected
+ if (result == 0)
+ return false;
+
+ // otherwise add to bytes read
+ bytesRead += result;
+ }
+ return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/Telepathy/Pool.cs b/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/Telepathy/Pool.cs
new file mode 100644
index 0000000..4ec4fd2
--- /dev/null
+++ b/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/Telepathy/Pool.cs
@@ -0,0 +1,34 @@
+// pool to avoid allocations. originally from libuv2k.
+using System;
+using System.Collections.Generic;
+
+namespace Telepathy
+{
+ public class Pool
+ {
+ // objects
+ readonly Stack objects = new Stack();
+
+ // some types might need additional parameters in their constructor, so
+ // we use a Func generator
+ readonly Func objectGenerator;
+
+ // constructor
+ public Pool(Func objectGenerator)
+ {
+ this.objectGenerator = objectGenerator;
+ }
+
+ // take an element from the pool, or create a new one if empty
+ public T Take() => objects.Count > 0 ? objects.Pop() : objectGenerator();
+
+ // return an element to the pool
+ public void Return(T item) => objects.Push(item);
+
+ // clear the pool with the disposer function applied to each object
+ public void Clear() => objects.Clear();
+
+ // count to see how many objects are in the pool. useful for tests.
+ public int Count() => objects.Count;
+ }
+}
\ No newline at end of file
diff --git a/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/Telepathy/SafeQueue.cs b/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/Telepathy/SafeQueue.cs
new file mode 100644
index 0000000..7899911
--- /dev/null
+++ b/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/Telepathy/SafeQueue.cs
@@ -0,0 +1 @@
+// removed 2021-02-04
diff --git a/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/Telepathy/Server.cs b/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/Telepathy/Server.cs
new file mode 100644
index 0000000..d044b50
--- /dev/null
+++ b/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/Telepathy/Server.cs
@@ -0,0 +1,394 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading;
+
+namespace Telepathy
+{
+ public class Server : Common
+ {
+ // events to hook into
+ // => OnData uses ArraySegment for allocation free receives later
+ public Action OnConnected;
+ public Action> OnData;
+ public Action OnDisconnected;
+
+ // listener
+ public TcpListener listener;
+ Thread listenerThread;
+
+ // disconnect if send queue gets too big.
+ // -> avoids ever growing queue memory if network is slower than input
+ // -> disconnecting is great for load balancing. better to disconnect
+ // one connection than risking every connection / the whole server
+ // -> huge queue would introduce multiple seconds of latency anyway
+ //
+ // Mirror/DOTSNET use MaxMessageSize batching, so for a 16kb max size:
+ // limit = 1,000 means 16 MB of memory/connection
+ // limit = 10,000 means 160 MB of memory/connection
+ public int SendQueueLimit = 10000;
+ public int ReceiveQueueLimit = 10000;
+
+ // thread safe pipe for received messages
+ // IMPORTANT: unfortunately using one pipe per connection is way slower
+ // when testing 150 CCU. we need to use one pipe for all
+ // connections. this scales beautifully.
+ protected MagnificentReceivePipe receivePipe;
+
+ // pipe count, useful for debugging / benchmarks
+ public int ReceivePipeTotalCount => receivePipe.TotalCount;
+
+ // clients with
+ readonly ConcurrentDictionary clients = new ConcurrentDictionary();
+
+ // connectionId counter
+ int counter;
+
+ // public next id function in case someone needs to reserve an id
+ // (e.g. if hostMode should always have 0 connection and external
+ // connections should start at 1, etc.)
+ public int NextConnectionId()
+ {
+ int id = Interlocked.Increment(ref counter);
+
+ // it's very unlikely that we reach the uint limit of 2 billion.
+ // even with 1 new connection per second, this would take 68 years.
+ // -> but if it happens, then we should throw an exception because
+ // the caller probably should stop accepting clients.
+ // -> it's hardly worth using 'bool Next(out id)' for that case
+ // because it's just so unlikely.
+ if (id == int.MaxValue)
+ {
+ throw new Exception("connection id limit reached: " + id);
+ }
+
+ return id;
+ }
+
+ // check if the server is running
+ public bool Active => listenerThread != null && listenerThread.IsAlive;
+
+ // constructor
+ public Server(int MaxMessageSize) : base(MaxMessageSize) {}
+
+ // the listener thread's listen function
+ // note: no maxConnections parameter. high level API should handle that.
+ // (Transport can't send a 'too full' message anyway)
+ void Listen(int port)
+ {
+ // absolutely must wrap with try/catch, otherwise thread
+ // exceptions are silent
+ try
+ {
+ // start listener on all IPv4 and IPv6 address via .Create
+ listener = TcpListener.Create(port);
+ listener.Server.NoDelay = NoDelay;
+ listener.Server.SendTimeout = SendTimeout;
+ listener.Server.ReceiveTimeout = ReceiveTimeout;
+ listener.Start();
+ Log.Info("Server: listening port=" + port);
+
+ // keep accepting new clients
+ while (true)
+ {
+ // wait and accept new client
+ // note: 'using' sucks here because it will try to
+ // dispose after thread was started but we still need it
+ // in the thread
+ TcpClient client = listener.AcceptTcpClient();
+
+ // set socket options
+ client.NoDelay = NoDelay;
+ client.SendTimeout = SendTimeout;
+ client.ReceiveTimeout = ReceiveTimeout;
+
+ // generate the next connection id (thread safely)
+ int connectionId = NextConnectionId();
+
+ // add to dict immediately
+ ConnectionState connection = new ConnectionState(client, MaxMessageSize);
+ clients[connectionId] = connection;
+
+ // spawn a send thread for each client
+ Thread sendThread = new Thread(() =>
+ {
+ // wrap in try-catch, otherwise Thread exceptions
+ // are silent
+ try
+ {
+ // run the send loop
+ // IMPORTANT: DO NOT SHARE STATE ACROSS MULTIPLE THREADS!
+ ThreadFunctions.SendLoop(connectionId, client, connection.sendPipe, connection.sendPending);
+ }
+ catch (ThreadAbortException)
+ {
+ // happens on stop. don't log anything.
+ // (we catch it in SendLoop too, but it still gets
+ // through to here when aborting. don't show an
+ // error.)
+ }
+ catch (Exception exception)
+ {
+ Log.Error("Server send thread exception: " + exception);
+ }
+ });
+ sendThread.IsBackground = true;
+ sendThread.Start();
+
+ // spawn a receive thread for each client
+ Thread receiveThread = new Thread(() =>
+ {
+ // wrap in try-catch, otherwise Thread exceptions
+ // are silent
+ try
+ {
+ // run the receive loop
+ // (receive pipe is shared across all loops)
+ ThreadFunctions.ReceiveLoop(connectionId, client, MaxMessageSize, receivePipe, ReceiveQueueLimit);
+
+ // IMPORTANT: do NOT remove from clients after the
+ // thread ends. need to do it in Tick() so that the
+ // disconnect event in the pipe is still processed.
+ // (removing client immediately would mean that the
+ // pipe is lost and the disconnect event is never
+ // processed)
+
+ // sendthread might be waiting on ManualResetEvent,
+ // so let's make sure to end it if the connection
+ // closed.
+ // otherwise the send thread would only end if it's
+ // actually sending data while the connection is
+ // closed.
+ sendThread.Interrupt();
+ }
+ catch (Exception exception)
+ {
+ Log.Error("Server client thread exception: " + exception);
+ }
+ });
+ receiveThread.IsBackground = true;
+ receiveThread.Start();
+ }
+ }
+ catch (ThreadAbortException exception)
+ {
+ // UnityEditor causes AbortException if thread is still
+ // running when we press Play again next time. that's okay.
+ Log.Info("Server thread aborted. That's okay. " + exception);
+ }
+ catch (SocketException exception)
+ {
+ // calling StopServer will interrupt this thread with a
+ // 'SocketException: interrupted'. that's okay.
+ Log.Info("Server Thread stopped. That's okay. " + exception);
+ }
+ catch (Exception exception)
+ {
+ // something went wrong. probably important.
+ Log.Error("Server Exception: " + exception);
+ }
+ }
+
+ // start listening for new connections in a background thread and spawn
+ // a new thread for each one.
+ public bool Start(int port)
+ {
+ // not if already started
+ if (Active) return false;
+
+ // create receive pipe with max message size for pooling
+ // => create new pipes every time!
+ // if an old receive thread is still finishing up, it might still
+ // be using the old pipes. we don't want to risk any old data for
+ // our new start here.
+ receivePipe = new MagnificentReceivePipe(MaxMessageSize);
+
+ // start the listener thread
+ // (on low priority. if main thread is too busy then there is not
+ // much value in accepting even more clients)
+ Log.Info("Server: Start port=" + port);
+ listenerThread = new Thread(() => { Listen(port); });
+ listenerThread.IsBackground = true;
+ listenerThread.Priority = ThreadPriority.BelowNormal;
+ listenerThread.Start();
+ return true;
+ }
+
+ public void Stop()
+ {
+ // only if started
+ if (!Active) return;
+
+ Log.Info("Server: stopping...");
+
+ // stop listening to connections so that no one can connect while we
+ // close the client connections
+ // (might be null if we call Stop so quickly after Start that the
+ // thread was interrupted before even creating the listener)
+ listener?.Stop();
+
+ // kill listener thread at all costs. only way to guarantee that
+ // .Active is immediately false after Stop.
+ // -> calling .Join would sometimes wait forever
+ listenerThread?.Interrupt();
+ listenerThread = null;
+
+ // close all client connections
+ foreach (KeyValuePair kvp in clients)
+ {
+ TcpClient client = kvp.Value.client;
+ // close the stream if not closed yet. it may have been closed
+ // by a disconnect already, so use try/catch
+ try { client.GetStream().Close(); } catch {}
+ client.Close();
+ }
+
+ // clear clients list
+ clients.Clear();
+
+ // reset the counter in case we start up again so
+ // clients get connection ID's starting from 1
+ counter = 0;
+ }
+
+ // send message to client using socket connection.
+ // arraysegment for allocation free sends later.
+ // -> the segment's array is only used until Send() returns!
+ public bool Send(int connectionId, ArraySegment message)
+ {
+ // respect max message size to avoid allocation attacks.
+ if (message.Count <= MaxMessageSize)
+ {
+ // find the connection
+ if (clients.TryGetValue(connectionId, out ConnectionState connection))
+ {
+ // check send pipe limit
+ if (connection.sendPipe.Count < SendQueueLimit)
+ {
+ // add to thread safe send pipe and return immediately.
+ // calling Send here would be blocking (sometimes for long
+ // times if other side lags or wire was disconnected)
+ connection.sendPipe.Enqueue(message);
+ connection.sendPending.Set(); // interrupt SendThread WaitOne()
+ return true;
+ }
+ // disconnect if send queue gets too big.
+ // -> avoids ever growing queue memory if network is slower
+ // than input
+ // -> disconnecting is great for load balancing. better to
+ // disconnect one connection than risking every
+ // connection / the whole server
+ //
+ // note: while SendThread always grabs the WHOLE send queue
+ // immediately, it's still possible that the sending
+ // blocks for so long that the send queue just gets
+ // way too big. have a limit - better safe than sorry.
+ else
+ {
+ // log the reason
+ Log.Warning($"Server.Send: sendPipe for connection {connectionId} reached limit of {SendQueueLimit}. This can happen if we call send faster than the network can process messages. Disconnecting this connection for load balancing.");
+
+ // just close it. send thread will take care of the rest.
+ connection.client.Close();
+ return false;
+ }
+ }
+
+ // sending to an invalid connectionId is expected sometimes.
+ // for example, if a client disconnects, the server might still
+ // try to send for one frame before it calls GetNextMessages
+ // again and realizes that a disconnect happened.
+ // so let's not spam the console with log messages.
+ //Logger.Log("Server.Send: invalid connectionId: " + connectionId);
+ return false;
+ }
+ Log.Error("Server.Send: message too big: " + message.Count + ". Limit: " + MaxMessageSize);
+ return false;
+ }
+
+ // client's ip is sometimes needed by the server, e.g. for bans
+ public string GetClientAddress(int connectionId)
+ {
+ // find the connection
+ if (clients.TryGetValue(connectionId, out ConnectionState connection))
+ {
+ return ((IPEndPoint)connection.client.Client.RemoteEndPoint).Address.ToString();
+ }
+ return "";
+ }
+
+ // disconnect (kick) a client
+ public bool Disconnect(int connectionId)
+ {
+ // find the connection
+ if (clients.TryGetValue(connectionId, out ConnectionState connection))
+ {
+ // just close it. send thread will take care of the rest.
+ connection.client.Close();
+ Log.Info("Server.Disconnect connectionId:" + connectionId);
+ return true;
+ }
+ return false;
+ }
+
+ // tick: processes up to 'limit' messages for each connection
+ // => limit parameter to avoid deadlocks / too long freezes if server or
+ // client is too slow to process network load
+ // => Mirror & DOTSNET need to have a process limit anyway.
+ // might as well do it here and make life easier.
+ // => returns amount of remaining messages to process, so the caller
+ // can call tick again as many times as needed (or up to a limit)
+ //
+ // Tick() may process multiple messages, but Mirror needs a way to stop
+ // processing immediately if a scene change messages arrives. Mirror
+ // can't process any other messages during a scene change.
+ // (could be useful for others too)
+ // => make sure to allocate the lambda only once in transports
+ public int Tick(int processLimit, Func checkEnabled = null)
+ {
+ // only if pipe was created yet (after start())
+ if (receivePipe == null)
+ return 0;
+
+ // process up to 'processLimit' messages for this connection
+ for (int i = 0; i < processLimit; ++i)
+ {
+ // check enabled in case a Mirror scene message arrived
+ if (checkEnabled != null && !checkEnabled())
+ break;
+
+ // peek first. allows us to process the first queued entry while
+ // still keeping the pooled byte[] alive by not removing anything.
+ if (receivePipe.TryPeek(out int connectionId, out EventType eventType, out ArraySegment message))
+ {
+ switch (eventType)
+ {
+ case EventType.Connected:
+ OnConnected?.Invoke(connectionId);
+ break;
+ case EventType.Data:
+ OnData?.Invoke(connectionId, message);
+ break;
+ case EventType.Disconnected:
+ OnDisconnected?.Invoke(connectionId);
+ // remove disconnected connection now that the final
+ // disconnected message was processed.
+ clients.TryRemove(connectionId, out ConnectionState _);
+ break;
+ }
+
+ // IMPORTANT: now dequeue and return it to pool AFTER we are
+ // done processing the event.
+ receivePipe.TryDequeue();
+ }
+ // no more messages. stop the loop.
+ else break;
+ }
+
+ // return what's left to process for next time
+ return receivePipe.TotalCount;
+ }
+ }
+}
diff --git a/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/Telepathy/ThreadExtensions.cs b/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/Telepathy/ThreadExtensions.cs
new file mode 100644
index 0000000..85dece4
--- /dev/null
+++ b/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/Telepathy/ThreadExtensions.cs
@@ -0,0 +1 @@
+// removed 2021-02-04
diff --git a/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/Telepathy/ThreadFunctions.cs b/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/Telepathy/ThreadFunctions.cs
new file mode 100644
index 0000000..6f026c9
--- /dev/null
+++ b/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/Telepathy/ThreadFunctions.cs
@@ -0,0 +1,244 @@
+// IMPORTANT
+// force all thread functions to be STATIC.
+// => Common.Send/ReceiveLoop is EXTREMELY DANGEROUS because it's too easy to
+// accidentally share Common state between threads.
+// => header buffer, payload etc. were accidentally shared once after changing
+// the thread functions from static to non static
+// => C# does not automatically detect data races. best we can do is move all of
+// our thread code into static functions and pass all state into them
+//
+// let's even keep them in a STATIC CLASS so it's 100% obvious that this should
+// NOT EVER be changed to non static!
+using System;
+using System.Net.Sockets;
+using System.Threading;
+
+namespace Telepathy
+{
+ public static class ThreadFunctions
+ {
+ // send message (via stream) with the message structure
+ // this function is blocking sometimes!
+ // (e.g. if someone has high latency or wire was cut off)
+ // -> payload is of multiple < parts
+ public static bool SendMessagesBlocking(NetworkStream stream, byte[] payload, int packetSize)
+ {
+ // stream.Write throws exceptions if client sends with high
+ // frequency and the server stops
+ try
+ {
+ // write the whole thing
+ stream.Write(payload, 0, packetSize);
+ return true;
+ }
+ catch (Exception exception)
+ {
+ // log as regular message because servers do shut down sometimes
+ Log.Info("Send: stream.Write exception: " + exception);
+ return false;
+ }
+ }
+ // read message (via stream) blocking.
+ // writes into byte[] and returns bytes written to avoid allocations.
+ public static bool ReadMessageBlocking(NetworkStream stream, int MaxMessageSize, byte[] headerBuffer, byte[] payloadBuffer, out int size)
+ {
+ size = 0;
+
+ // buffer needs to be of Header + MaxMessageSize
+ if (payloadBuffer.Length != 4 + MaxMessageSize)
+ {
+ Log.Error($"ReadMessageBlocking: payloadBuffer needs to be of size 4 + MaxMessageSize = {4 + MaxMessageSize} instead of {payloadBuffer.Length}");
+ return false;
+ }
+
+ // read exactly 4 bytes for header (blocking)
+ if (!stream.ReadExactly(headerBuffer, 4))
+ return false;
+
+ // convert to int
+ size = Utils.BytesToIntBigEndian(headerBuffer);
+
+ // protect against allocation attacks. an attacker might send
+ // multiple fake '2GB header' packets in a row, causing the server
+ // to allocate multiple 2GB byte arrays and run out of memory.
+ //
+ // also protect against size <= 0 which would cause issues
+ if (size > 0 && size <= MaxMessageSize)
+ {
+ // read exactly 'size' bytes for content (blocking)
+ return stream.ReadExactly(payloadBuffer, size);
+ }
+ Log.Warning("ReadMessageBlocking: possible header attack with a header of: " + size + " bytes.");
+ return false;
+ }
+
+ // thread receive function is the same for client and server's clients
+ public static void ReceiveLoop(int connectionId, TcpClient client, int MaxMessageSize, MagnificentReceivePipe receivePipe, int QueueLimit)
+ {
+ // get NetworkStream from client
+ NetworkStream stream = client.GetStream();
+
+ // every receive loop needs it's own receive buffer of
+ // HeaderSize + MaxMessageSize
+ // to avoid runtime allocations.
+ //
+ // IMPORTANT: DO NOT make this a member, otherwise every connection
+ // on the server would use the same buffer simulatenously
+ byte[] receiveBuffer = new byte[4 + MaxMessageSize];
+
+ // avoid header[4] allocations
+ //
+ // IMPORTANT: DO NOT make this a member, otherwise every connection
+ // on the server would use the same buffer simulatenously
+ byte[] headerBuffer = new byte[4];
+
+ // absolutely must wrap with try/catch, otherwise thread exceptions
+ // are silent
+ try
+ {
+ // add connected event to pipe
+ receivePipe.Enqueue(connectionId, EventType.Connected, default);
+
+ // let's talk about reading data.
+ // -> normally we would read as much as possible and then
+ // extract as many , messages
+ // as we received this time. this is really complicated
+ // and expensive to do though
+ // -> instead we use a trick:
+ // Read(2) -> size
+ // Read(size) -> content
+ // repeat
+ // Read is blocking, but it doesn't matter since the
+ // best thing to do until the full message arrives,
+ // is to wait.
+ // => this is the most elegant AND fast solution.
+ // + no resizing
+ // + no extra allocations, just one for the content
+ // + no crazy extraction logic
+ while (true)
+ {
+ // read the next message (blocking) or stop if stream closed
+ if (!ReadMessageBlocking(stream, MaxMessageSize, headerBuffer, receiveBuffer, out int size))
+ // break instead of return so stream close still happens!
+ break;
+
+ // create arraysegment for the read message
+ ArraySegment message = new ArraySegment(receiveBuffer, 0, size);
+
+ // send to main thread via pipe
+ // -> it'll copy the message internally so we can reuse the
+ // receive buffer for next read!
+ receivePipe.Enqueue(connectionId, EventType.Data, message);
+
+ // disconnect if receive pipe gets too big for this connectionId.
+ // -> avoids ever growing queue memory if network is slower
+ // than input
+ // -> disconnecting is great for load balancing. better to
+ // disconnect one connection than risking every
+ // connection / the whole server
+ if (receivePipe.Count(connectionId) >= QueueLimit)
+ {
+ // log the reason
+ Log.Warning($"receivePipe reached limit of {QueueLimit} for connectionId {connectionId}. This can happen if network messages come in way faster than we manage to process them. Disconnecting this connection for load balancing.");
+
+ // IMPORTANT: do NOT clear the whole queue. we use one
+ // queue for all connections.
+ //receivePipe.Clear();
+
+ // just break. the finally{} will close everything.
+ break;
+ }
+ }
+ }
+ catch (Exception exception)
+ {
+ // something went wrong. the thread was interrupted or the
+ // connection closed or we closed our own connection or ...
+ // -> either way we should stop gracefully
+ Log.Info("ReceiveLoop: finished receive function for connectionId=" + connectionId + " reason: " + exception);
+ }
+ finally
+ {
+ // clean up no matter what
+ stream.Close();
+ client.Close();
+
+ // add 'Disconnected' message after disconnecting properly.
+ // -> always AFTER closing the streams to avoid a race condition
+ // where Disconnected -> Reconnect wouldn't work because
+ // Connected is still true for a short moment before the stream
+ // would be closed.
+ receivePipe.Enqueue(connectionId, EventType.Disconnected, default);
+ }
+ }
+ // thread send function
+ // note: we really do need one per connection, so that if one connection
+ // blocks, the rest will still continue to get sends
+ public static void SendLoop(int connectionId, TcpClient client, MagnificentSendPipe sendPipe, ManualResetEvent sendPending)
+ {
+ // get NetworkStream from client
+ NetworkStream stream = client.GetStream();
+
+ // avoid payload[packetSize] allocations. size increases dynamically as
+ // needed for batching.
+ //
+ // IMPORTANT: DO NOT make this a member, otherwise every connection
+ // on the server would use the same buffer simulatenously
+ byte[] payload = null;
+
+ try
+ {
+ while (client.Connected) // try this. client will get closed eventually.
+ {
+ // reset ManualResetEvent before we do anything else. this
+ // way there is no race condition. if Send() is called again
+ // while in here then it will be properly detected next time
+ // -> otherwise Send might be called right after dequeue but
+ // before .Reset, which would completely ignore it until
+ // the next Send call.
+ sendPending.Reset(); // WaitOne() blocks until .Set() again
+
+ // dequeue & serialize all
+ // a locked{} TryDequeueAll is twice as fast as
+ // ConcurrentQueue, see SafeQueue.cs!
+ if (sendPipe.DequeueAndSerializeAll(ref payload, out int packetSize))
+ {
+ // send messages (blocking) or stop if stream is closed
+ if (!SendMessagesBlocking(stream, payload, packetSize))
+ // break instead of return so stream close still happens!
+ break;
+ }
+
+ // don't choke up the CPU: wait until queue not empty anymore
+ sendPending.WaitOne();
+ }
+ }
+ catch (ThreadAbortException)
+ {
+ // happens on stop. don't log anything.
+ }
+ catch (ThreadInterruptedException)
+ {
+ // happens if receive thread interrupts send thread.
+ }
+ catch (Exception exception)
+ {
+ // something went wrong. the thread was interrupted or the
+ // connection closed or we closed our own connection or ...
+ // -> either way we should stop gracefully
+ Log.Info("SendLoop Exception: connectionId=" + connectionId + " reason: " + exception);
+ }
+ finally
+ {
+ // clean up no matter what
+ // we might get SocketExceptions when sending if the 'host has
+ // failed to respond' - in which case we should close the connection
+ // which causes the ReceiveLoop to end and fire the Disconnected
+ // message. otherwise the connection would stay alive forever even
+ // though we can't send anymore.
+ stream.Close();
+ client.Close();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/Telepathy/Utils.cs b/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/Telepathy/Utils.cs
new file mode 100644
index 0000000..8f04fe9
--- /dev/null
+++ b/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/Telepathy/Utils.cs
@@ -0,0 +1,23 @@
+namespace Telepathy
+{
+ public static class Utils
+ {
+ // IntToBytes version that doesn't allocate a new byte[4] each time.
+ // -> important for MMO scale networking performance.
+ public static void IntToBytesBigEndianNonAlloc(int value, byte[] bytes, int offset = 0)
+ {
+ bytes[offset + 0] = (byte)(value >> 24);
+ bytes[offset + 1] = (byte)(value >> 16);
+ bytes[offset + 2] = (byte)(value >> 8);
+ bytes[offset + 3] = (byte)value;
+ }
+
+ public static int BytesToIntBigEndian(byte[] bytes)
+ {
+ return (bytes[0] << 24) |
+ (bytes[1] << 16) |
+ (bytes[2] << 8) |
+ bytes[3];
+ }
+ }
+}
diff --git a/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/Telepathy/VERSION b/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/Telepathy/VERSION
new file mode 100644
index 0000000..b70eef2
--- /dev/null
+++ b/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/Telepathy/VERSION
@@ -0,0 +1,58 @@
+V1.7 [2021-02-20]
+- ReceiveTimeout: disabled by default for cases where people use Telepathy by
+ itself without pings etc.
+
+V1.6 [2021-02-10]
+- configurable ReceiveTimeout to avoid TCPs high default timeout
+- Server/Client receive queue limit now disconnects instead of showing a
+ warning. this is necessary for load balancing to avoid situations where one
+ spamming connection might fill the queue and slow down everyone else.
+
+V1.5 [2021-02-05]
+- fix: client data races & flaky tests fixed by creating a new client state
+ object every time we connect. fixes data race where an old dieing thread
+ might still try to modify the current state
+- fix: Client.ReceiveThreadFunction catches and ignores ObjectDisposedException
+ which can happen if Disconnect() closes and disposes the client, while the
+ ReceiveThread just starts up and still uses the client.
+- Server/Client Tick() optional enabled check for Mirror scene changing
+
+V1.4 [2021-02-03]
+- Server/Client.Tick: limit parameter added to process up to 'limit' messages.
+ makes Mirror & DOTSNET transports easier to implement
+- stability: Server/Client send queue limit disconnects instead of showing a
+ warning. allows for load balancing. better to kick one connection and keep
+ the server running than slowing everything down for everyone.
+
+V1.3 [2021-02-02]
+- perf: ReceivePipe: byte[] pool for allocation free receives (╯°□°)╯︵ ┻━┻
+- fix: header buffer, payload buffer data races because they were made non
+ static earlier. server threads would all access the same ones.
+ => all threaded code was moved into a static ThreadFunctions class to make it
+ 100% obvious that there should be no shared state in the future
+
+V1.2 [2021-02-02]
+- Client/Server Tick & OnConnected/OnData/OnDisconnected events instead of
+ having the outside process messages via GetNextMessage. That's easier for
+ Mirror/DOTSNET and allows for allocation free data message processing later.
+- MagnificientSend/RecvPipe to shield Telepathy from all the complexity
+- perf: SendPipe: byte[] pool for allocation free sends (╯°□°)╯︵ ┻━┻
+
+V1.1 [2021-02-01]
+- stability: added more tests
+- breaking: Server/Client.Send: ArraySegment parameter and copy internally so
+ that Transports don't need to worry about it
+- perf: Buffer.BlockCopy instead of Array.Copy
+- perf: SendMessageBlocking puts message header directly into payload now
+- perf: receiveQueues use SafeQueue instead of ConcurrentQueue to avoid
+ allocations
+- Common: removed static state
+- perf: SafeQueue.TryDequeueAll: avoid queue.ToArray() allocations. copy into a
+ list instead.
+- Logger.Log/LogWarning/LogError renamed to Log.Info/Warning/Error
+- MaxMessageSize is now specified in constructor to prepare for pooling
+- flaky tests are ignored for now
+- smaller improvements
+
+V1.0
+- first stable release
\ No newline at end of file
diff --git a/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/TelepathyConfig.cs b/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/TelepathyConfig.cs
new file mode 100644
index 0000000..ef4c29b
--- /dev/null
+++ b/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/TelepathyConfig.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Mirror
+{
+ class TelepathyConfig
+ {
+ public bool NoDelay = true;
+
+ public int SendTimeout = 5000;
+
+ public int ReceiveTimeout = 30000;
+
+ public int serverMaxMessageSize = 16 * 1024;
+
+ public int serverMaxReceivesPerTick = 10000;
+
+ public int serverSendQueueLimitPerConnection = 10000;
+
+ public int serverReceiveQueueLimitPerConnection = 10000;
+ }
+}
diff --git a/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/TelepathyTransport.cs b/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/TelepathyTransport.cs
new file mode 100644
index 0000000..ed46425
--- /dev/null
+++ b/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Telepathy/TelepathyTransport.cs
@@ -0,0 +1,195 @@
+// wraps Telepathy for use as HLAPI TransportLayer
+using Newtonsoft.Json;
+using System;
+using System.IO;
+using System.Net;
+using System.Net.Sockets;
+
+// Replaced by Kcp November 2020
+namespace Mirror
+{
+ public class TelepathyTransport : Transport
+ {
+ // scheme used by this transport
+ // "tcp4" means tcp with 4 bytes header, network byte order
+ public const string Scheme = "tcp4";
+
+ public bool NoDelay = true;
+
+ public int SendTimeout = 5000;
+
+ public int ReceiveTimeout = 30000;
+
+ public int serverMaxMessageSize = 16 * 1024;
+
+ public int serverMaxReceivesPerTick = 10000;
+
+ public int serverSendQueueLimitPerConnection = 10000;
+
+ public int serverReceiveQueueLimitPerConnection = 10000;
+
+ public int clientMaxMessageSize = 16 * 1024;
+
+ public int clientMaxReceivesPerTick = 1000;
+
+ public int clientSendQueueLimit = 10000;
+
+ public int clientReceiveQueueLimit = 10000;
+
+ Telepathy.Client client;
+ Telepathy.Server server;
+
+ // scene change message needs to halt message processing immediately
+ // Telepathy.Tick() has a enabledCheck parameter that we can use, but
+ // let's only allocate it once.
+ Func enabledCheck;
+
+ void Awake()
+ {
+ TelepathyConfig conf = new TelepathyConfig();
+ if (!File.Exists("TelepathyConfig.json"))
+ {
+ File.WriteAllText("TelepathyConfig.json", JsonConvert.SerializeObject(conf, Formatting.Indented));
+ }
+ else
+ {
+ conf = JsonConvert.DeserializeObject(File.ReadAllText("TelepathyConfig.json"));
+ }
+
+ NoDelay = conf.NoDelay;
+ SendTimeout = conf.SendTimeout;
+ ReceiveTimeout = conf.ReceiveTimeout;
+ serverMaxMessageSize = conf.serverMaxMessageSize;
+ serverMaxReceivesPerTick = conf.serverMaxReceivesPerTick;
+ serverSendQueueLimitPerConnection = conf.serverSendQueueLimitPerConnection;
+ serverReceiveQueueLimitPerConnection = conf.serverReceiveQueueLimitPerConnection;
+
+ // create client & server
+ client = new Telepathy.Client(clientMaxMessageSize);
+ server = new Telepathy.Server(serverMaxMessageSize);
+
+ // tell Telepathy to use Unity's Debug.Log
+ Telepathy.Log.Info = Console.WriteLine;
+ Telepathy.Log.Warning = Console.WriteLine;
+ Telepathy.Log.Error = Console.WriteLine;
+
+ // client hooks
+ // other systems hook into transport events in OnCreate or
+ // OnStartRunning in no particular order. the only way to avoid
+ // race conditions where telepathy uses OnConnected before another
+ // system's hook (e.g. statistics OnData) was added is to wrap
+ // them all in a lambda and always call the latest hook.
+ // (= lazy call)
+ client.OnConnected = () => OnClientConnected.Invoke();
+ client.OnData = (segment) => OnClientDataReceived.Invoke(segment, 0);
+ client.OnDisconnected = () => OnClientDisconnected.Invoke();
+
+ // client configuration
+ client.NoDelay = NoDelay;
+ client.SendTimeout = SendTimeout;
+ client.ReceiveTimeout = ReceiveTimeout;
+ client.SendQueueLimit = clientSendQueueLimit;
+ client.ReceiveQueueLimit = clientReceiveQueueLimit;
+
+ // server hooks
+ // other systems hook into transport events in OnCreate or
+ // OnStartRunning in no particular order. the only way to avoid
+ // race conditions where telepathy uses OnConnected before another
+ // system's hook (e.g. statistics OnData) was added is to wrap
+ // them all in a lambda and always call the latest hook.
+ // (= lazy call)
+ server.OnConnected = (connectionId) => OnServerConnected.Invoke(connectionId);
+ server.OnData = (connectionId, segment) => OnServerDataReceived.Invoke(connectionId, segment, 0);
+ server.OnDisconnected = (connectionId) => OnServerDisconnected.Invoke(connectionId);
+
+ // server configuration
+ server.NoDelay = NoDelay;
+ server.SendTimeout = SendTimeout;
+ server.ReceiveTimeout = ReceiveTimeout;
+ server.SendQueueLimit = serverSendQueueLimitPerConnection;
+ server.ReceiveQueueLimit = serverReceiveQueueLimitPerConnection;
+
+ // allocate enabled check only once
+ enabledCheck = () => true;
+
+ Console.WriteLine("TelepathyTransport initialized!");
+ }
+
+ public override bool Available()
+ {
+ // C#'s built in TCP sockets run everywhere except on WebGL
+ return true;
+ }
+
+ // client
+ public override bool ClientConnected() => client.Connected;
+ public override void ClientConnect(string address) { }
+ public override void ClientConnect(Uri uri) { }
+ public override void ClientSend(int channelId, ArraySegment segment) => client.Send(segment);
+ public override void ClientDisconnect() => client.Disconnect();
+ // messages should always be processed in early update
+
+ // server
+ public override Uri ServerUri()
+ {
+ UriBuilder builder = new UriBuilder();
+ builder.Scheme = Scheme;
+ builder.Host = Dns.GetHostName();
+ return builder.Uri;
+ }
+ public override bool ServerActive() => server.Active;
+ public override void ServerStart(ushort requestedPort) => server.Start(requestedPort);
+ public override void ServerSend(int connectionId, int channelId, ArraySegment segment) => server.Send(connectionId, segment);
+ public override bool ServerDisconnect(int connectionId) => server.Disconnect(connectionId);
+ public override string ServerGetClientAddress(int connectionId)
+ {
+ try
+ {
+ return server.GetClientAddress(connectionId);
+ }
+ catch (SocketException)
+ {
+ // using server.listener.LocalEndpoint causes an Exception
+ // in UWP + Unity 2019:
+ // Exception thrown at 0x00007FF9755DA388 in UWF.exe:
+ // Microsoft C++ exception: Il2CppExceptionWrapper at memory
+ // location 0x000000E15A0FCDD0. SocketException: An address
+ // incompatible with the requested protocol was used at
+ // System.Net.Sockets.Socket.get_LocalEndPoint ()
+ // so let's at least catch it and recover
+ return "unknown";
+ }
+ }
+ public override void ServerStop() => server.Stop();
+ // messages should always be processed in early update
+ public void LateUpdate()
+ {
+ // note: we need to check enabled in case we set it to false
+ // when LateUpdate already started.
+ // (https://github.com/vis2k/Mirror/pull/379)
+
+ // process a maximum amount of server messages per tick
+ // IMPORTANT: check .enabled to stop processing immediately after a
+ // scene change message arrives!
+ server.Tick(serverMaxReceivesPerTick, enabledCheck);
+ }
+
+ // common
+ public override void Shutdown()
+ {
+ Console.WriteLine("TelepathyTransport Shutdown()");
+ client.Disconnect();
+ server.Stop();
+ }
+
+ public override int GetMaxPacketSize(int channelId)
+ {
+ return serverMaxMessageSize;
+ }
+
+ public override string ToString()
+ {
+ return "Telepathy";
+ }
+ }
+}
diff --git a/UnityProject/Assets/Mirror/Examples/Tanks/Scenes/Scene.unity b/UnityProject/Assets/Mirror/Examples/Tanks/Scenes/Scene.unity
index cbbe18d..804c241 100644
--- a/UnityProject/Assets/Mirror/Examples/Tanks/Scenes/Scene.unity
+++ b/UnityProject/Assets/Mirror/Examples/Tanks/Scenes/Scene.unity
@@ -54,7 +54,7 @@ LightmapSettings:
m_EnableBakedLightmaps: 0
m_EnableRealtimeLightmaps: 0
m_LightmapEditorSettings:
- serializedVersion: 12
+ serializedVersion: 10
m_Resolution: 2
m_BakeResolution: 40
m_AtlasSize: 1024
@@ -62,7 +62,6 @@ LightmapSettings:
m_AOMaxDistance: 1
m_CompAOExponent: 1
m_CompAOExponentDirect: 0
- m_ExtractAmbientOcclusion: 0
m_Padding: 2
m_LightmapParameters: {fileID: 0}
m_LightmapsBakeMode: 1
@@ -77,16 +76,10 @@ LightmapSettings:
m_PVRDirectSampleCount: 32
m_PVRSampleCount: 500
m_PVRBounces: 2
- m_PVREnvironmentSampleCount: 500
- m_PVREnvironmentReferencePointCount: 2048
- m_PVRFilteringMode: 2
- m_PVRDenoiserTypeDirect: 0
- m_PVRDenoiserTypeIndirect: 0
- m_PVRDenoiserTypeAO: 0
m_PVRFilterTypeDirect: 0
m_PVRFilterTypeIndirect: 0
m_PVRFilterTypeAO: 0
- m_PVREnvironmentMIS: 0
+ m_PVRFilteringMode: 1
m_PVRCulling: 1
m_PVRFilteringGaussRadiusDirect: 1
m_PVRFilteringGaussRadiusIndirect: 5
@@ -94,9 +87,7 @@ LightmapSettings:
m_PVRFilteringAtrousPositionSigmaDirect: 0.5
m_PVRFilteringAtrousPositionSigmaIndirect: 2
m_PVRFilteringAtrousPositionSigmaAO: 1
- m_ExportTrainingData: 0
- m_TrainingDataDestination: TrainingData
- m_LightProbeSampleCountMultiplier: 4
+ m_ShowResolutionOverlay: 1
m_LightingDataAsset: {fileID: 0}
m_UseShadowmask: 1
--- !u!196 &4
@@ -150,10 +141,9 @@ Camera:
m_ClearFlags: 1
m_BackGroundColor: {r: 0, g: 0, b: 0, a: 0}
m_projectionMatrixMode: 1
- m_GateFitMode: 2
- m_FOVAxisMode: 0
m_SensorSize: {x: 36, y: 24}
m_LensShift: {x: 0, y: 0}
+ m_GateFitMode: 2
m_FocalLength: 50
m_NormalizedViewPortRect:
serializedVersion: 2
@@ -195,136 +185,6 @@ Transform:
m_Father: {fileID: 0}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 45, y: 180, z: 0}
---- !u!1 &171810013
-GameObject:
- m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInstance: {fileID: 0}
- m_PrefabAsset: {fileID: 0}
- serializedVersion: 6
- m_Component:
- - component: {fileID: 171810014}
- - component: {fileID: 171810017}
- - component: {fileID: 171810016}
- - component: {fileID: 171810015}
- m_Layer: 5
- m_Name: Button
- m_TagString: Untagged
- m_Icon: {fileID: 0}
- m_NavMeshLayer: 0
- m_StaticEditorFlags: 0
- m_IsActive: 1
---- !u!224 &171810014
-RectTransform:
- m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInstance: {fileID: 0}
- m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 171810013}
- m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
- m_LocalPosition: {x: 0, y: 0, z: 0}
- m_LocalScale: {x: 1, y: 1, z: 1}
- m_Children:
- - {fileID: 2007463783}
- m_Father: {fileID: 1933302372}
- m_RootOrder: 0
- m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
- m_AnchorMin: {x: 0.5, y: 0.5}
- m_AnchorMax: {x: 0.5, y: 0.5}
- m_AnchoredPosition: {x: 0, y: 0}
- m_SizeDelta: {x: 160, y: 30}
- m_Pivot: {x: 0.5, y: 0.5}
---- !u!114 &171810015
-MonoBehaviour:
- m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInstance: {fileID: 0}
- m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 171810013}
- m_Enabled: 1
- m_EditorHideFlags: 0
- m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3}
- m_Name:
- m_EditorClassIdentifier:
- m_Navigation:
- m_Mode: 3
- m_SelectOnUp: {fileID: 0}
- m_SelectOnDown: {fileID: 0}
- m_SelectOnLeft: {fileID: 0}
- m_SelectOnRight: {fileID: 0}
- m_Transition: 1
- m_Colors:
- m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
- m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
- m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
- m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
- m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
- m_ColorMultiplier: 1
- m_FadeDuration: 0.1
- m_SpriteState:
- m_HighlightedSprite: {fileID: 0}
- m_PressedSprite: {fileID: 0}
- m_SelectedSprite: {fileID: 0}
- m_DisabledSprite: {fileID: 0}
- m_AnimationTriggers:
- m_NormalTrigger: Normal
- m_HighlightedTrigger: Highlighted
- m_PressedTrigger: Pressed
- m_SelectedTrigger: Selected
- m_DisabledTrigger: Disabled
- m_Interactable: 1
- m_TargetGraphic: {fileID: 171810016}
- m_OnClick:
- m_PersistentCalls:
- m_Calls:
- - m_Target: {fileID: 1282001523}
- m_MethodName: RequestServerList
- m_Mode: 1
- m_Arguments:
- m_ObjectArgument: {fileID: 0}
- m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine
- m_IntArgument: 0
- m_FloatArgument: 0
- m_StringArgument:
- m_BoolArgument: 0
- m_CallState: 2
---- !u!114 &171810016
-MonoBehaviour:
- m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInstance: {fileID: 0}
- m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 171810013}
- m_Enabled: 1
- m_EditorHideFlags: 0
- m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
- m_Name:
- m_EditorClassIdentifier:
- m_Material: {fileID: 0}
- m_Color: {r: 1, g: 1, b: 1, a: 1}
- m_RaycastTarget: 1
- m_Maskable: 1
- m_OnCullStateChanged:
- m_PersistentCalls:
- m_Calls: []
- m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0}
- m_Type: 1
- m_PreserveAspect: 0
- m_FillCenter: 1
- m_FillMethod: 4
- m_FillAmount: 1
- m_FillClockwise: 1
- m_FillOrigin: 0
- m_UseSpriteMesh: 0
- m_PixelsPerUnitMultiplier: 1
---- !u!222 &171810017
-CanvasRenderer:
- m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInstance: {fileID: 0}
- m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 171810013}
- m_CullTransparentMesh: 0
--- !u!1 &251893064
GameObject:
m_ObjectHideFlags: 0
@@ -411,59 +271,6 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 41f84591ce72545258ea98cb7518d8b9, type: 3}
m_Name:
m_EditorClassIdentifier:
---- !u!1 &769288735
-GameObject:
- m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInstance: {fileID: 0}
- m_PrefabAsset: {fileID: 0}
- serializedVersion: 6
- m_Component:
- - component: {fileID: 769288737}
- - component: {fileID: 769288736}
- m_Layer: 0
- m_Name: Puncher
- m_TagString: Untagged
- m_Icon: {fileID: 0}
- m_NavMeshLayer: 0
- m_StaticEditorFlags: 0
- m_IsActive: 1
---- !u!114 &769288736
-MonoBehaviour:
- m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInstance: {fileID: 0}
- m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 769288735}
- m_Enabled: 1
- m_EditorHideFlags: 0
- m_Script: {fileID: 11500000, guid: 6b0fecffa3f624585964b0d0eb21b18e, type: 3}
- m_Name:
- m_EditorClassIdentifier:
- Port: 1111
- NoDelay: 1
- Interval: 10
- FastResend: 2
- CongestionWindow: 0
- SendWindowSize: 4096
- ReceiveWindowSize: 4096
- debugLog: 1
- statisticsGUI: 1
- statisticsLog: 0
---- !u!4 &769288737
-Transform:
- m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInstance: {fileID: 0}
- m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 769288735}
- m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
- m_LocalPosition: {x: 0, y: 0, z: 0}
- m_LocalScale: {x: 1, y: 1, z: 1}
- m_Children: []
- m_Father: {fileID: 1282001518}
- m_RootOrder: 0
- m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &1107091652
GameObject:
m_ObjectHideFlags: 0
@@ -497,7 +304,6 @@ MeshRenderer:
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
- m_RayTracingMode: 2
m_RenderingLayerMask: 4294967295
m_RendererPriority: 0
m_Materials:
@@ -509,7 +315,6 @@ MeshRenderer:
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
- m_ReceiveGI: 1
m_PreserveUVs: 1
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
@@ -532,9 +337,9 @@ MeshCollider:
m_Material: {fileID: 0}
m_IsTrigger: 0
m_Enabled: 1
- serializedVersion: 4
+ serializedVersion: 3
m_Convex: 0
- m_CookingOptions: 30
+ m_CookingOptions: 14
m_Mesh: {fileID: 10209, guid: 0000000000000000e000000000000000, type: 0}
--- !u!33 &1107091655
MeshFilter:
@@ -558,72 +363,6 @@ Transform:
m_Father: {fileID: 0}
m_RootOrder: 1
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
---- !u!1 &1143775647
-GameObject:
- m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInstance: {fileID: 0}
- m_PrefabAsset: {fileID: 0}
- serializedVersion: 6
- m_Component:
- - component: {fileID: 1143775650}
- - component: {fileID: 1143775649}
- - component: {fileID: 1143775648}
- m_Layer: 0
- m_Name: EventSystem
- m_TagString: Untagged
- m_Icon: {fileID: 0}
- m_NavMeshLayer: 0
- m_StaticEditorFlags: 0
- m_IsActive: 1
---- !u!114 &1143775648
-MonoBehaviour:
- m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInstance: {fileID: 0}
- m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 1143775647}
- m_Enabled: 1
- m_EditorHideFlags: 0
- m_Script: {fileID: 11500000, guid: 4f231c4fb786f3946a6b90b886c48677, type: 3}
- m_Name:
- m_EditorClassIdentifier:
- m_HorizontalAxis: Horizontal
- m_VerticalAxis: Vertical
- m_SubmitButton: Submit
- m_CancelButton: Cancel
- m_InputActionsPerSecond: 10
- m_RepeatDelay: 0.5
- m_ForceModuleActive: 0
---- !u!114 &1143775649
-MonoBehaviour:
- m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInstance: {fileID: 0}
- m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 1143775647}
- m_Enabled: 1
- m_EditorHideFlags: 0
- m_Script: {fileID: 11500000, guid: 76c392e42b5098c458856cdf6ecaaaa1, type: 3}
- m_Name:
- m_EditorClassIdentifier:
- m_FirstSelected: {fileID: 0}
- m_sendNavigationEvents: 1
- m_DragThreshold: 10
---- !u!4 &1143775650
-Transform:
- m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInstance: {fileID: 0}
- m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 1143775647}
- m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
- m_LocalPosition: {x: 0, y: 0, z: 0}
- m_LocalScale: {x: 1, y: 1, z: 1}
- m_Children: []
- m_Father: {fileID: 0}
- m_RootOrder: 9
- m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &1282001517
GameObject:
m_ObjectHideFlags: 0
@@ -636,8 +375,6 @@ GameObject:
- component: {fileID: 1282001520}
- component: {fileID: 1282001519}
- component: {fileID: 1282001521}
- - component: {fileID: 1282001523}
- - component: {fileID: 1282001522}
m_Layer: 0
m_Name: NetworkManager
m_TagString: Untagged
@@ -655,8 +392,7 @@ Transform:
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
- m_Children:
- - {fileID: 769288737}
+ m_Children: []
m_Father: {fileID: 0}
m_RootOrder: 3
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
@@ -688,15 +424,15 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
dontDestroyOnLoad: 1
- PersistNetworkManagerToOfflineScene: 0
runInBackground: 1
autoStartServerBuild: 1
+ showDebugMessages: 0
serverTickRate: 30
serverBatching: 0
serverBatchInterval: 0
offlineScene:
onlineScene:
- transport: {fileID: 1282001523}
+ transport: {fileID: 1282001521}
networkAddress: localhost
maxConnections: 100
disconnectInactiveConnections: 0
@@ -730,51 +466,6 @@ MonoBehaviour:
debugLog: 0
statisticsGUI: 0
statisticsLog: 0
---- !u!114 &1282001522
-MonoBehaviour:
- m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInstance: {fileID: 0}
- m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 1282001517}
- m_Enabled: 1
- m_EditorHideFlags: 0
- m_Script: {fileID: 11500000, guid: 9c4cbff877abc42448dd829920c6c233, type: 3}
- m_Name:
- m_EditorClassIdentifier:
- directConnectTransport: {fileID: 769288736}
- showDebugLogs: 1
---- !u!114 &1282001523
-MonoBehaviour:
- m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInstance: {fileID: 0}
- m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 1282001517}
- m_Enabled: 1
- m_EditorHideFlags: 0
- m_Script: {fileID: 11500000, guid: 7064b1b1d0671194baf55fa8d5f564d6, type: 3}
- m_Name:
- m_EditorClassIdentifier:
- clientToServerTransport: {fileID: 1282001521}
- serverIP: 172.105.109.117
- endpointServerPort: 8080
- heartBeatInterval: 3
- connectOnAwake: 1
- authenticationKey: Secret Auth Key
- diconnectedFromRelay:
- m_PersistentCalls:
- m_Calls: []
- useNATPunch: 1
- NATPunchtroughPort: 7776
- serverName: My awesome server!
- extraServerData: Map 1
- maxServerPlayers: 10
- isPublicServer: 1
- serverListUpdated:
- m_PersistentCalls:
- m_Calls: []
- serverId: -1
--- !u!1 &1458789072
GameObject:
m_ObjectHideFlags: 0
@@ -861,183 +552,6 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 41f84591ce72545258ea98cb7518d8b9, type: 3}
m_Name:
m_EditorClassIdentifier:
---- !u!1 &1933302368
-GameObject:
- m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInstance: {fileID: 0}
- m_PrefabAsset: {fileID: 0}
- serializedVersion: 6
- m_Component:
- - component: {fileID: 1933302372}
- - component: {fileID: 1933302371}
- - component: {fileID: 1933302370}
- - component: {fileID: 1933302369}
- m_Layer: 5
- m_Name: Canvas
- m_TagString: Untagged
- m_Icon: {fileID: 0}
- m_NavMeshLayer: 0
- m_StaticEditorFlags: 0
- m_IsActive: 1
---- !u!114 &1933302369
-MonoBehaviour:
- m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInstance: {fileID: 0}
- m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 1933302368}
- m_Enabled: 1
- m_EditorHideFlags: 0
- m_Script: {fileID: 11500000, guid: dc42784cf147c0c48a680349fa168899, type: 3}
- m_Name:
- m_EditorClassIdentifier:
- m_IgnoreReversedGraphics: 1
- m_BlockingObjects: 0
- m_BlockingMask:
- serializedVersion: 2
- m_Bits: 4294967295
---- !u!114 &1933302370
-MonoBehaviour:
- m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInstance: {fileID: 0}
- m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 1933302368}
- m_Enabled: 1
- m_EditorHideFlags: 0
- m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3}
- m_Name:
- m_EditorClassIdentifier:
- m_UiScaleMode: 0
- m_ReferencePixelsPerUnit: 100
- m_ScaleFactor: 1
- m_ReferenceResolution: {x: 800, y: 600}
- m_ScreenMatchMode: 0
- m_MatchWidthOrHeight: 0
- m_PhysicalUnit: 3
- m_FallbackScreenDPI: 96
- m_DefaultSpriteDPI: 96
- m_DynamicPixelsPerUnit: 1
---- !u!223 &1933302371
-Canvas:
- m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInstance: {fileID: 0}
- m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 1933302368}
- m_Enabled: 1
- serializedVersion: 3
- m_RenderMode: 0
- m_Camera: {fileID: 0}
- m_PlaneDistance: 100
- m_PixelPerfect: 0
- m_ReceivesEvents: 1
- m_OverrideSorting: 0
- m_OverridePixelPerfect: 0
- m_SortingBucketNormalizedSize: 0
- m_AdditionalShaderChannelsFlag: 0
- m_SortingLayerID: 0
- m_SortingOrder: 0
- m_TargetDisplay: 0
---- !u!224 &1933302372
-RectTransform:
- m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInstance: {fileID: 0}
- m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 1933302368}
- m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
- m_LocalPosition: {x: 0, y: 0, z: 0}
- m_LocalScale: {x: 0, y: 0, z: 0}
- m_Children:
- - {fileID: 171810014}
- m_Father: {fileID: 0}
- m_RootOrder: 8
- m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
- m_AnchorMin: {x: 0, y: 0}
- m_AnchorMax: {x: 0, y: 0}
- m_AnchoredPosition: {x: 0, y: 0}
- m_SizeDelta: {x: 0, y: 0}
- m_Pivot: {x: 0, y: 0}
---- !u!1 &2007463782
-GameObject:
- m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInstance: {fileID: 0}
- m_PrefabAsset: {fileID: 0}
- serializedVersion: 6
- m_Component:
- - component: {fileID: 2007463783}
- - component: {fileID: 2007463785}
- - component: {fileID: 2007463784}
- m_Layer: 5
- m_Name: Text
- m_TagString: Untagged
- m_Icon: {fileID: 0}
- m_NavMeshLayer: 0
- m_StaticEditorFlags: 0
- m_IsActive: 1
---- !u!224 &2007463783
-RectTransform:
- m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInstance: {fileID: 0}
- m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 2007463782}
- m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
- m_LocalPosition: {x: 0, y: 0, z: 0}
- m_LocalScale: {x: 1, y: 1, z: 1}
- m_Children: []
- m_Father: {fileID: 171810014}
- m_RootOrder: 0
- m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
- m_AnchorMin: {x: 0, y: 0}
- m_AnchorMax: {x: 1, y: 1}
- m_AnchoredPosition: {x: 0, y: 0}
- m_SizeDelta: {x: 0, y: 0}
- m_Pivot: {x: 0.5, y: 0.5}
---- !u!114 &2007463784
-MonoBehaviour:
- m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInstance: {fileID: 0}
- m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 2007463782}
- m_Enabled: 1
- m_EditorHideFlags: 0
- m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3}
- m_Name:
- m_EditorClassIdentifier:
- m_Material: {fileID: 0}
- m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1}
- m_RaycastTarget: 1
- m_Maskable: 1
- m_OnCullStateChanged:
- m_PersistentCalls:
- m_Calls: []
- m_FontData:
- m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0}
- m_FontSize: 14
- m_FontStyle: 0
- m_BestFit: 0
- m_MinSize: 10
- m_MaxSize: 40
- m_Alignment: 4
- m_AlignByGeometry: 0
- m_RichText: 1
- m_HorizontalOverflow: 0
- m_VerticalOverflow: 0
- m_LineSpacing: 1
- m_Text: Button
---- !u!222 &2007463785
-CanvasRenderer:
- m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInstance: {fileID: 0}
- m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 2007463782}
- m_CullTransparentMesh: 0
--- !u!1 &2054208274
GameObject:
m_ObjectHideFlags: 0
@@ -1063,14 +577,12 @@ Light:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2054208274}
m_Enabled: 1
- serializedVersion: 10
+ serializedVersion: 8
m_Type: 1
- m_Shape: 0
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_Intensity: 1
m_Range: 10
m_SpotAngle: 30
- m_InnerSpotAngle: 21.80208
m_CookieSize: 10
m_Shadows:
m_Type: 2
@@ -1080,24 +592,6 @@ Light:
m_Bias: 0.05
m_NormalBias: 0.4
m_NearPlane: 0.2
- m_CullingMatrixOverride:
- e00: 1
- e01: 0
- e02: 0
- e03: 0
- e10: 0
- e11: 1
- e12: 0
- e13: 0
- e20: 0
- e21: 0
- e22: 1
- e23: 0
- e30: 0
- e31: 0
- e32: 0
- e33: 1
- m_UseCullingMatrixOverride: 0
m_Cookie: {fileID: 0}
m_DrawHalo: 0
m_Flare: {fileID: 0}
@@ -1105,15 +599,12 @@ Light:
m_CullingMask:
serializedVersion: 2
m_Bits: 4294967295
- m_RenderingLayerMask: 1
m_Lightmapping: 4
m_LightShadowCasterMode: 0
m_AreaSize: {x: 1, y: 1}
m_BounceIntensity: 1
m_ColorTemperature: 6570
m_UseColorTemperature: 0
- m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0}
- m_UseBoundingSphereOverride: 0
m_ShadowRadius: 0
m_ShadowAngle: 0
--- !u!4 &2054208276
diff --git a/UnityProject/ProjectSettings/ProjectSettings.asset b/UnityProject/ProjectSettings/ProjectSettings.asset
index 63e9fad..fd89bc2 100644
--- a/UnityProject/ProjectSettings/ProjectSettings.asset
+++ b/UnityProject/ProjectSettings/ProjectSettings.asset
@@ -68,7 +68,7 @@ PlayerSettings:
androidBlitType: 0
defaultIsNativeResolution: 1
macRetinaSupport: 1
- runInBackground: 0
+ runInBackground: 1
captureSingleScreen: 0
muteOtherAudioSources: 0
Prepare IOS For Recording: 0
@@ -163,7 +163,7 @@ PlayerSettings:
useHDRDisplay: 0
D3DHDRBitDepth: 0
m_ColorGamuts: 00000000
- targetPixelDensity: 0
+ targetPixelDensity: 30
resolutionScalingMode: 0
androidSupportedAspectRatio: 1
androidMaxAspectRatio: 2.1
@@ -185,10 +185,10 @@ PlayerSettings:
StripUnusedMeshComponents: 0
VertexChannelCompressionMask: 4054
iPhoneSdkVersion: 988
- iOSTargetOSVersionString:
+ iOSTargetOSVersionString: 10.0
tvOSSdkVersion: 0
tvOSRequireExtendedGameController: 0
- tvOSTargetOSVersionString:
+ tvOSTargetOSVersionString: 10.0
uIPrerenderedIcon: 0
uIRequiresPersistentWiFi: 0
uIRequiresFullScreen: 1