moving main source tree into trunk

This commit is contained in:
spoogemonster
2011-05-11 03:58:10 +00:00
parent 7df38c9097
commit b59a2b6be3
128 changed files with 25439 additions and 0 deletions

View File

@@ -0,0 +1,10 @@
* The NetBuffer object is gone; instead there are NetOutgoingMessage and NetIncomingMessage objects
* No need to allocate a read buffer before calling ReadMessage

View File

@@ -0,0 +1,113 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<style type="text/css">
body
{
font-family: Verdana, Geneva, Arial, sans-serif;
font-size: small;
margin-left: 20px;
background-color: #dddddd;
}
td
{
font-family: Verdana, Geneva, Arial, sans-serif;
font-size: small;
}
.page
{
width: 700px;
}
.cf
{
font-family: Courier New;
font-size: 10pt;
color: black;
background: white;
padding: 16px;
border: 1px solid black;
}
.cl
{
margin: 0px;
}
.cb1
{
color: green;
}
.cb2
{
color: #2b91af;
}
.cb3
{
color: blue;
}
.cb4
{
color: #a31515;
}
</style>
<title>Peer/server discovery</title>
</head>
<body>
<table>
<tr>
<td class="page">
<h1>Peer/server discovery</h1>
<p>
Peer discovery is the process of clients detecting what servers are available. Discovery requests can be made in two ways;
locally as a broadcast, which will send a signal to all peers on your subnet. Secondly you can contact an ip address directly
and query it if a server is running.
</p>
<p>Responding to discovery requests are done in the same way regardless of how the request is made.</p>
<p>Here's how to do on the client side; ie. the side which makes a request:</p>
<div class="cf">
<pre class="cl"><span class="cb1">// Enable DiscoveryResponse messages</span></pre>
<pre class="cl">config.EnableMessageType(<span class="cb2">NetIncomingMessageType</span>.DiscoveryResponse);</pre>
<pre class="cl">&nbsp;</pre>
<pre class="cl"><span class="cb1">// Emit a discovery signal</span></pre>
<pre class="cl">Client.DiscoverLocalPeers(14242);</pre>
</div>
<p>This will send a discovery signal to your subnet; Here's how to receive the signal on the server side, and send a response back to the client:</p>
<div class="cf">
<pre class="cl"><span class="cb1">// Enable DiscoveryRequest messages</span></pre>
<pre class="cl">config.EnableMessageType(<span class="cb2">NetIncomingMessageType</span>.DiscoveryRequest);</pre>
<pre class="cl">&nbsp;</pre>
<pre class="cl"><span class="cb1">// Standard message reading loop</span></pre>
<pre class="cl"><span class="cb3">while</span> ((inc = Server.ReadMessage()) != <span class="cb1">null</span>)</pre>
<pre class="cl">{</pre>
<pre class="cl">&nbsp;&nbsp;&nbsp; <span class="cb3">switch</span> (inc.MessageType)</pre>
<pre class="cl">&nbsp;&nbsp;&nbsp; {</pre>
<pre class="cl">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; <span class="cb3">case</span> <span class="cb2">NetIncomingMessageType</span>.DiscoveryRequest:</pre>
<pre class="cl">&nbsp;</pre>
<pre class="cl">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; <span class="cb1">// Create a response and write some example data to it</span></pre>
<pre class="cl">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; <span class="cb2">NetOutgoingMessage</span> response = Server.CreateMessage();</pre>
<pre class="cl">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; response.Write(<span class="cb4">&quot;My server name&quot;</span>);</pre>
<pre class="cl">&nbsp;</pre>
<pre class="cl">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; <span class="cb1">// Send the response to the sender of the request</span></pre>
<pre class="cl">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; Server.SendDiscoveryResponse(response, inc.SenderEndpoint);</pre>
<pre class="cl">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; <span class="cb3">break</span>;</pre>
</div>
<p>When the response then reaches the client, you can read the data you wrote on the server:</p>
<div class="cf">
<pre class="cl"><span class="cb1">// Standard message reading loop</span></pre>
<pre class="cl"><span class="cb3">while</span> ((inc = Client.ReadMessage()) != <span class="cb1">null</span>)</pre>
<pre class="cl">{</pre>
<pre class="cl">&nbsp;&nbsp;&nbsp; <span class="cb3">switch</span> (inc.MessageType)</pre>
<pre class="cl">&nbsp;&nbsp;&nbsp; {</pre>
<pre class="cl">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; <span class="cb3">case</span> <span class="cb2">NetIncomingMessageType</span>.DiscoveryResponse:</pre>
<pre class="cl">&nbsp;</pre>
<pre class="cl">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; <span class="cb2">Console</span>.WriteLine(<span class="cb4">&quot;Found server at &quot;</span> + inc.SenderEndpoint + <span class="cb4">&quot; name: &quot;</span> + inc.ReadString());</pre>
<pre class="cl">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; <span class="cb3">break</span>;</pre>
</div>
</td>
</tr>
</table>
</body>
</html>

View File

@@ -0,0 +1,22 @@
Improvements over last version of library:
* New delivery type: Reliable sequenced (Lost packets are resent but late arrivals are dropped)
* Disconnects and shutdown requests are now queued properly, so calling shutdown will still send any queued messages before shutting down
* All messages are pooled/recycled for zero garbage
* Reduced CPU usage and lower latencies (in the <1 ms range, but still) due to better socket polling
* All public members of NetPeer/NetConnection are completely thread safe
* Larger number of delivery channels
* More exact roundtrip measurement
* Method serialize entire objects via reflection
* Unique identifier now exists for all peers/connections
* More flexible peer discovery; filters possible and arbitrary data can be sent with response
* Much better protection against malformed messages crashing the app
API enhancements:
* NetPeerConfiguration immutable properties now locked once NetPeer is initialized
* Messages cannot be send twice by accident
* Impossible to confuse sending and receiving buffers since they're different classes
* No more confusion if user should create a buffer or preallocate and reuse

View File

@@ -0,0 +1,17 @@
PER MESSAGE:
7 bits - NetMessageType
1 bit - Is a message fragment?
[8 bits NetMessageLibraryType, if NetMessageType == Library]
[16 bits sequence number, if NetMessageType >= UserSequenced]
8/16 bits - Payload length in bits (variable size ushort)
[16 bits fragments group id, if fragmented]
[16 bits fragments total count, if fragmented]
[16 bits fragment number, if fragmented]
[x - Payload] if length > 0

View File

@@ -0,0 +1,115 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<style type="text/css">
body
{
font-family: Verdana, Geneva, Arial, sans-serif;
font-size: small;
margin-left: 20px;
background-color: #dddddd;
}
td
{
font-family: Verdana, Geneva, Arial, sans-serif;
font-size: small;
}
.cf
{
font-family: Courier New;
font-size: 10pt;
color: black;
background: white;
padding: 16px;
border: 1px solid black;
}
.cl
{
margin: 0px;
}
.cb1
{
color: green;
}
.cb2
{
color: #2b91af;
}
.cb3
{
color: blue;
}
.cb4
{
color: #a31515;
}
</style>
<title>Lidgren tutorial</title>
</head>
<body>
<table>
<tr>
<td width="700">
<h1>Simulating bad network conditions</h1>
<p>
On the internet, your packets are likely to run in to all kinds of trouble. They will be delayed and lost and they might even arrive multiple times at the destination. Lidgren has a few option to simulate how your application or game will react when this happens.<br />
They are all configured using the NetPeerConfiguration class - these properties exists:</p>
<p>
<div class="cf">
<table>
<tr>
<td valign="top">
<b>SimulatedLoss</b>
</td>
<td>
&nbsp;
</td>
<td valign="top">
This is a float which simulates lost packets. A value of 0 will disable this feature, a value of 0.5f will make half of your sent packets disappear, chosen randomly. Note that packets may contain several messages - this is the amount of packets lost.
</td>
</tr>
<tr>
<td colspan="3">
&nbsp;
</td>
</tr>
<tr>
<td valign="top">
<b>SimulatedDuplicatesChance</b>
</td>
<td>
&nbsp;
</td>
<td valign="top">
This is a float which determines the chance that a packet will be duplicated at the destination. 0 means no packets will be duplicated, 0.5f means that on average, every other packet will be duplicated.
</td>
</tr>
<tr>
<td colspan="3">
&nbsp;
</td>
</tr>
<tr>
<td valign="top">
<b>SimulatedMinimumLatency</b><br />
<b>SimulatedRandomLatency</b>
</td>
<td>
&nbsp;
</td>
<td valign="top">
These two properties control simulating delay of packets in seconds (not milliseconds, use 0.05 for 50 ms of lag). They work on top of the actual network delay and the total delay will be:<br />
Actual one way latency + SimulatedMinimumLatency + [Randomly per packet 0 to SimulatedRandomLatency seconds]
</td>
</tr>
</table>
</div>
<p>It's recommended to assume symmetric condtions and configure server and client with the same simulation settings.</p>
<p>Simulating bad network conditions only works in DEBUG builds.</p>
</td>
</tr>
</table>
</body>
</html>

View File

@@ -0,0 +1,17 @@
Completed features:
* Message coalescing
* Peer, connection statistics
* Lag, loss and duplication simulation for testing
* Connection approval
* Throttling
* Clock synchronization to detect jitter per packet (NetTime.RemoteNow)
* Peer discovery
* Message fragmentation
Missing features:
* Receipts 25% done, need design
* More realistic lag/loss (lumpy)
* Detect estimated packet loss
* More advanced ack packet

View File

@@ -0,0 +1,206 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<style type="text/css">
body
{
font-family: Verdana, Geneva, Arial, sans-serif;
font-size: small;
margin-left: 20px;
background-color: #dddddd;
}
td
{
font-family: Verdana, Geneva, Arial, sans-serif;
font-size: small;
}
.cf
{
font-family: Courier New;
font-size: 10pt;
color: black;
background: white;
padding: 16px;
border: 1px solid black;
}
.cl
{
margin: 0px;
}
.cb1
{
color: green;
}
.cb2
{
color: #2b91af;
}
.cb3
{
color: blue;
}
.cb4
{
color: #a31515;
}
</style>
<title>Lidgren basics tutorial</title>
</head>
<body>
<table>
<tr>
<td width="700">
<h1>
Lidgren basics</h1>
<p>
Lidgren network library is all about messages. There are two types of messages:</p>
<li>Library messages telling you things like a peer has connected or diagnostics messages (warnings, errors) when unexpected things happen.</li>
<li>Data messages which is data sent from a remote (connected or unconnected) peer.</li>
<p>
The base class for establishing connections, receiving and sending message are the NetPeer class. Using it you can make a peer-to-peer network, but if you are creating a server/client topology there are special classes called NetServer and NetClient. They inherit NetPeer but sets some defaults and includes some helper methods/properties.</p>
<p>
Here's how to set up a NetServer:</p>
<div class="cf">
<pre class="cl"><span class="cb2">NetPeerConfiguration</span> config = <span class="cb3">new</span> <span class="cb2">NetPeerConfiguration</span>(<span class="cb4">&quot;MyExampleName&quot;</span>);</pre>
<pre class="cl">config.Port = 14242;</pre>
<pre class="cl">&nbsp;</pre>
<pre class="cl"><span class="cb2">NetServer</span> server = <span class="cb3">new</span> <span class="cb2">NetServer</span>(config);</pre>
<pre class="cl">server.Start();</pre>
</div>
<p>
The code above first creates a configuration. It has lots of properties you can change, but the default values should be pretty good for most applications. The string you provide in the constructor (MyExampleName) is an identifier to distinquish it from other applications using the lidgren library. Just make sure you use the same string in both server and client - or you will be unable to communicate between them.</p>
<p>
Secondly we've set the local port the server should listen to. This is the port number we tell the client(s) what port number to connect to. The local port can be set for a client too, but it's not needed and not recommended.</p>
<p>
Thirdly we create our server object and fourth we Start() it. Starting the server will create a new network thread and bind to a socket and start listening for connections.</p>
<p>
Early on we spoke about messages; now is the time to start receiving and sending some. Here's a code snippet for receiving messages:</p>
<div class="cf">
<pre class="cl"><span class="cb2">NetIncomingMessage</span> msg;</pre>
<pre class="cl"><span class="cb3">while</span> ((msg = server.ReadMessage()) != <span class="cb3">null</span>)</pre>
<pre class="cl">{</pre>
<pre class="cl">&nbsp;&nbsp;&nbsp; <span class="cb3">switch</span> (msg.MessageType)</pre>
<pre class="cl">&nbsp;&nbsp;&nbsp; {</pre>
<pre class="cl">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; <span class="cb3">case</span> <span class="cb2">NetIncomingMessageType</span>.VerboseDebugMessage:</pre>
<pre class="cl">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; <span class="cb3">case</span> <span class="cb2">NetIncomingMessageType</span>.DebugMessage:</pre>
<pre class="cl">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; <span class="cb3">case</span> <span class="cb2">NetIncomingMessageType</span>.WarningMessage:</pre>
<pre class="cl">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; <span class="cb3">case</span> <span class="cb2">NetIncomingMessageType</span>.ErrorMessage:</pre>
<pre class="cl">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; <span class="cb2">Console</span>.WriteLine(msg.ReadString());</pre>
<pre class="cl">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; <span class="cb3">break</span>;</pre>
<pre class="cl">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; <span class="cb3">default</span>:</pre>
<pre class="cl">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; <span class="cb2">Console</span>.WriteLine(<span class="cb4">&quot;Unhandled type: &quot;</span> + msg.MessageType);</pre>
<pre class="cl">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; <span class="cb3">break</span>;</pre>
<pre class="cl">&nbsp;&nbsp;&nbsp; }</pre>
<pre class="cl">&nbsp;&nbsp;&nbsp; server.Recycle(msg);</pre>
<pre class="cl">}</pre>
</div>
<p>
So, lets dissect the above code. First we declare a NetIncomingMessage, which is the type of incoming messages. Then we read a message and handles it, looping back as long as there are messages to fetch. For each message we find, we switch on sometime called MessageType - it's a description what the message contains. In this code example we only catch messages of type VerboseDebugMessage, DebugMessage, WarningMessage and ErrorMessage. All those four types are emitted by the library to inform about various events. They all contains a single string, so we use the method ReadString() to extract a copy of that string and print it in the console.</p>
<p>
Reading data will increment the internal message pointer so you can read subsequent data using the Read*() methods.</p>
<p>
For all other message type we just print that it's currently unhandled.</p>
<p>
Finally, we recycle the message after we're done with it - this will enable the library to reuse the object and create less garbage.</p>
<p>
Sending messages are even easier:</p>
<div class="cf">
<pre class="cl"><span class="cb2">NetOutgoingMessage</span> sendMsg = server.CreateMessage();</pre>
<pre class="cl">sendMsg.Write(<span class="cb4">&quot;Hello&quot;</span>);</pre>
<pre class="cl">sendMsg.Write(42);</pre>
<pre class="cl">&nbsp;</pre>
<pre class="cl">server.SendMessage(sendMsg, recipient, <span class="cb2">NetDeliveryMethod</span>.ReliableOrdered);</pre>
</div>
<p>
The above code first creates a new message, or uses a recycled message, which is why it's not possible to just create a message using new(). It then writes a string ("Hello") and an integer (System.Int32, 4 bytes in size) to the message.</p>
<p>
Then the message is sent using the SendMessage() method. The first argument is the message to send, the second argument is the recipient connection - which we'll not go into detail about just yet - and the third argument are HOW to deliver the message, or rather how to behave if network conditions are bad and a packet gets lost, duplicated or reordered.</p>
<p>
There are five delivery methods available:</p>
<div class="cf">
<table>
<tr>
<td valign="top">
<b>Unreliable</b>
</td>
<td>
&nbsp;
</td>
<td valign="top">
This is just UDP. Messages can be lost, received more than once and messages sent after other messages may be received before them.
</td>
</tr>
<tr>
<td colspan="3">
&nbsp;
</td>
</tr>
<tr>
<td valign="top">
<b>UnreliableSequenced</b>
</td>
<td>
&nbsp;
</td>
<td valign="top">
Using this delivery method messages can still be lost; but you're protected against duplicated messages and if a message arrives late; that is, if a message sent after this one has already been received - it will be dropped. This means you will never receive "older" data than what you already have received.
</td>
</tr>
<tr>
<td colspan="3">
&nbsp;
</td>
</tr>
<tr>
<td valign="top">
<b>ReliableUnordered</b>
</td>
<td>
&nbsp;
</td>
<td valign="top">
This delivery method ensures that every message sent will be received eventually. It does not however guarantee what order they will be received; late messages may be delivered before older ones.
</td>
</tr>
<tr>
<td colspan="3">
&nbsp;
</td>
</tr>
<tr>
<td valign="top">
<b>ReliableSequenced</b>
</td>
<td>
&nbsp;
</td>
<td valign="top">
This delivery method is similar to UnreliableSequenced; except that is guarantees that SOME messages will be received - if you only send one message - it will be received. If you sent two messages quickly, and they get reordered in transit, only the newest message will be received - but at least ONE of them will be received guaranteed.
</td>
</tr>
<tr>
<td colspan="3">
&nbsp;
</td>
</tr>
<tr>
<td valign="top"><b>ReliableOrdered</b></td>
<td>&nbsp;</td>
<td valign="top">
This delivery method guarantees that messages will always be received in the exact order they were sent.
</td>
</tr>
</table>
</div>
<p>
Here's how to read and decode the message above:</p>
<div class="cf">
<pre class="cl"><span class="cb2">NetIncomingMessage</span> incMsg = server.ReadMessage();</pre>
<pre class="cl"><span class="cb3">string</span> str = incMsg.ReadString();</pre>
<pre class="cl"><span class="cb3">int</span> a = incMsg.ReadInt32();</pre>
</div>
</td>
</tr>
</table>
</body>
</html>

View File

@@ -0,0 +1,116 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="3.5" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>9.0.21022</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{AE483C29-042E-4226-BA52-D247CE7676DA}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Lidgren.Network</RootNamespace>
<AssemblyName>Lidgren.Network</AssemblyName>
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<SccProjectName>
</SccProjectName>
<SccLocalPath>
</SccLocalPath>
<SccAuxPath>
</SccAuxPath>
<SccProvider>
</SccProvider>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Debug\Lidgren.Network.XML</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Release\Lidgren.Network.XML</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="NamespaceDoc.cs" />
<Compile Include="NetBitVector.cs" />
<Compile Include="NetBitWriter.cs" />
<Compile Include="NetClient.cs" />
<Compile Include="NetConnection.cs" />
<Compile Include="NetConnection.Handshake.cs" />
<Compile Include="NetConnection.Latency.cs" />
<Compile Include="NetConnectionStatistics.cs" />
<Compile Include="NetConnectionStatus.cs" />
<Compile Include="NetConstants.cs" />
<Compile Include="NetDeliveryMethod.cs" />
<Compile Include="NetEncryption.cs" />
<Compile Include="NetException.cs" />
<Compile Include="NetFragmentationHelper.cs" />
<Compile Include="NetIncomingMessage.cs" />
<Compile Include="NetIncomingMessage.Peek.cs" />
<Compile Include="NetIncomingMessage.Read.cs" />
<Compile Include="NetIncomingMessage.Read.Reflection.cs" />
<Compile Include="NetIncomingMessage.Write.cs" />
<Compile Include="NetIncomingMessageType.cs" />
<Compile Include="NetMessageType.cs" />
<Compile Include="NetNatIntroduction.cs" />
<Compile Include="NetOutgoingMessage.cs" />
<Compile Include="NetOutgoingMessage.Write.cs" />
<Compile Include="NetOutgoingMessage.Write.Reflection.cs" />
<Compile Include="NetPeer.cs" />
<Compile Include="NetPeer.Discovery.cs" />
<Compile Include="NetPeer.Fragmentation.cs" />
<Compile Include="NetPeer.Internal.cs" />
<Compile Include="NetPeer.LatencySimulation.cs" />
<Compile Include="NetPeer.Logging.cs" />
<Compile Include="NetPeer.MessagePools.cs" />
<Compile Include="NetPeer.Send.cs" />
<Compile Include="NetPeerConfiguration.cs" />
<Compile Include="NetPeerStatistics.cs" />
<Compile Include="NetPeerStatus.cs" />
<Compile Include="NetQueue.cs" />
<Compile Include="NetRandom.cs" />
<Compile Include="NetReceiverChannelBase.cs" />
<Compile Include="NetReliableOrderedReceiver.cs" />
<Compile Include="NetReliableSenderChannel.cs" />
<Compile Include="NetReliableSequencedReceiver.cs" />
<Compile Include="NetReliableUnorderedReceiver.cs" />
<Compile Include="NetSenderChannelBase.cs" />
<Compile Include="NetSendResult.cs" />
<Compile Include="NetServer.cs" />
<Compile Include="NetStoredReliableMessage.cs" />
<Compile Include="NetTime.cs" />
<Compile Include="NetTuple.cs" />
<Compile Include="NetUnreliableSenderChannel.cs" />
<Compile Include="NetUnreliableSequencedReceiver.cs" />
<Compile Include="NetUnreliableUnorderedReceiver.cs" />
<Compile Include="NetUtility.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SenderChannelBase.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Lidgren.Network
{
/// <summary>
/// Lidgren Network Library
/// </summary>
internal class NamespaceDoc
{
// <include file='_Namespace.xml' path='Documentation/*' />
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,166 @@
/* Copyright (c) 2010 Michael Lidgren
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.
*/
using System;
using System.Text;
namespace Lidgren.Network
{
/// <summary>
/// Fixed size vector of booleans
/// </summary>
public sealed class NetBitVector
{
private readonly int m_capacity;
private readonly int[] m_data;
private int m_numBitsSet;
/// <summary>
/// Gets the number of bits/booleans stored in this vector
/// </summary>
public int Capacity { get { return m_capacity; } }
public NetBitVector(int bitsCapacity)
{
m_capacity = bitsCapacity;
m_data = new int[(bitsCapacity + 31) / 32];
}
/// <summary>
/// Returns true if all bits/booleans are set to zero/false
/// </summary>
public bool IsEmpty()
{
return (m_numBitsSet == 0);
}
/// <summary>
/// Returns the number of bits/booleans set to one/true
/// </summary>
/// <returns></returns>
public int Count()
{
return m_numBitsSet;
}
/// <summary>
/// Shift all bits one step down, cycling the first bit to the top
/// </summary>
public void RotateDown()
{
int lenMinusOne = m_data.Length - 1;
int firstBit = m_data[0] & 1;
for (int i = 0; i < lenMinusOne; i++)
m_data[i] = ((m_data[i] >> 1) & ~(1 << 31)) | m_data[i + 1] << 31;
int lastIndex = m_capacity - 1 - (32 * lenMinusOne);
// special handling of last int
int cur = m_data[lenMinusOne];
cur = cur >> 1;
cur |= firstBit << lastIndex;
m_data[lenMinusOne] = cur;
}
public int GetFirstSetIndex()
{
int idx = 0;
int data = m_data[0];
while (data == 0)
{
idx++;
data = m_data[idx];
}
int a = 0;
while (((data >> a) & 1) == 0)
a++;
return (idx * 32) + a;
}
/// <summary>
/// Gets the bit/bool at the specified index
/// </summary>
public bool Get(int bitIndex)
{
NetException.Assert(bitIndex >= 0 && bitIndex < m_capacity);
return (m_data[bitIndex / 32] & (1 << (bitIndex % 32))) != 0;
}
/// <summary>
/// Sets or clears the bit/bool at the specified index
/// </summary>
public void Set(int bitIndex, bool value)
{
NetException.Assert(bitIndex >= 0 && bitIndex < m_capacity);
int idx = bitIndex / 32;
if (value)
{
if ((m_data[idx] & (1 << (bitIndex % 32))) == 0)
m_numBitsSet++;
m_data[idx] |= (1 << (bitIndex % 32));
}
else
{
if ((m_data[idx] & (1 << (bitIndex % 32))) != 0)
m_numBitsSet--;
m_data[idx] &= (~(1 << (bitIndex % 32)));
}
}
/// <summary>
/// Gets the bit/bool at the specified index
/// </summary>
[System.Runtime.CompilerServices.IndexerName("Bit")]
public bool this[int index]
{
get { return Get(index); }
set { Set(index, value); }
}
/// <summary>
/// Sets all bits/booleans to zero/false
/// </summary>
public void Clear()
{
Array.Clear(m_data, 0, m_data.Length);
m_numBitsSet = 0;
NetException.Assert(this.IsEmpty());
}
/// <summary>
/// Returns a string that represents this object
/// </summary>
public override string ToString()
{
StringBuilder bdr = new StringBuilder(m_capacity + 2);
bdr.Append('[');
for (int i = 0; i < m_capacity; i++)
bdr.Append(Get(m_capacity - i - 1) ? '1' : '0');
bdr.Append(']');
return bdr.ToString();
}
}
}

View File

@@ -0,0 +1,414 @@
/* Copyright (c) 2010 Michael Lidgren
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.
*/
using System;
using System.Collections.Generic;
using System.Diagnostics;
namespace Lidgren.Network
{
/// <summary>
/// Helper class for NetBuffer to write/read bits
/// </summary>
public static class NetBitWriter
{
/// <summary>
/// Read 1-8 bits from a buffer into a byte
/// </summary>
public static byte ReadByte(byte[] fromBuffer, int numberOfBits, int readBitOffset)
{
NetException.Assert(((numberOfBits > 0) && (numberOfBits < 9)), "Read() can only read between 1 and 8 bits");
int bytePtr = readBitOffset >> 3;
int startReadAtIndex = readBitOffset - (bytePtr * 8); // (readBitOffset % 8);
if (startReadAtIndex == 0 && numberOfBits == 8)
return fromBuffer[bytePtr];
// mask away unused bits lower than (right of) relevant bits in first byte
byte returnValue = (byte)(fromBuffer[bytePtr] >> startReadAtIndex);
int numberOfBitsInSecondByte = numberOfBits - (8 - startReadAtIndex);
if (numberOfBitsInSecondByte < 1)
{
// we don't need to read from the second byte, but we DO need
// to mask away unused bits higher than (left of) relevant bits
return (byte)(returnValue & (255 >> (8 - numberOfBits)));
}
byte second = fromBuffer[bytePtr + 1];
// mask away unused bits higher than (left of) relevant bits in second byte
second &= (byte)(255 >> (8 - numberOfBitsInSecondByte));
return (byte)(returnValue | (byte)(second << (numberOfBits - numberOfBitsInSecondByte)));
}
/// <summary>
/// Read several bytes from a buffer
/// </summary>
public static void ReadBytes(byte[] fromBuffer, int numberOfBytes, int readBitOffset, byte[] destination, int destinationByteOffset)
{
int readPtr = readBitOffset >> 3;
int startReadAtIndex = readBitOffset - (readPtr * 8); // (readBitOffset % 8);
if (startReadAtIndex == 0)
{
Buffer.BlockCopy(fromBuffer, readPtr, destination, destinationByteOffset, numberOfBytes);
return;
}
int secondPartLen = 8 - startReadAtIndex;
int secondMask = 255 >> secondPartLen;
for (int i = 0; i < numberOfBytes; i++)
{
// mask away unused bits lower than (right of) relevant bits in byte
int b = fromBuffer[readPtr] >> startReadAtIndex;
readPtr++;
// mask away unused bits higher than (left of) relevant bits in second byte
int second = fromBuffer[readPtr] & secondMask;
destination[destinationByteOffset++] = (byte)(b | (second << secondPartLen));
}
return;
}
/// <summary>
/// Write a byte consisting of 1-8 bits to a buffer; assumes buffer is previously allocated
/// </summary>
public static void WriteByte(byte source, int numberOfBits, byte[] destination, int destBitOffset)
{
NetException.Assert(((numberOfBits >= 1) && (numberOfBits <= 8)), "Must write between 1 and 8 bits!");
// mask out unwanted bits in the source
byte isrc = (byte)((uint)source & ((~(uint)0) >> (8 - numberOfBits)));
int bytePtr = destBitOffset >> 3;
int localBitLen = (destBitOffset % 8);
if (localBitLen == 0)
{
destination[bytePtr] = (byte)isrc;
return;
}
//destination[bytePtr] &= (byte)(255 >> (8 - localBitLen)); // clear before writing
//destination[bytePtr] |= (byte)(isrc << localBitLen); // write first half
destination[bytePtr] = (byte)(
(uint)(destination[bytePtr] & (255 >> (8 - localBitLen))) |
(uint)(isrc << localBitLen)
);
// need write into next byte?
if (localBitLen + numberOfBits > 8)
{
//destination[bytePtr + 1] &= (byte)(255 << localBitLen); // clear before writing
//destination[bytePtr + 1] |= (byte)(isrc >> (8 - localBitLen)); // write second half
destination[bytePtr + 1] = (byte)(
(uint)(destination[bytePtr + 1] & (255 << localBitLen)) |
(uint)(isrc >> (8 - localBitLen))
);
}
return;
}
/// <summary>
/// Write several whole bytes
/// </summary>
public static void WriteBytes(byte[] source, int sourceByteOffset, int numberOfBytes, byte[] destination, int destBitOffset)
{
int dstBytePtr = destBitOffset >> 3;
int firstPartLen = (destBitOffset % 8);
if (firstPartLen == 0)
{
Buffer.BlockCopy(source, sourceByteOffset, destination, dstBytePtr, numberOfBytes);
return;
}
int lastPartLen = 8 - firstPartLen;
for (int i = 0; i < numberOfBytes; i++)
{
byte src = source[sourceByteOffset + i];
// write last part of this byte
destination[dstBytePtr] &= (byte)(255 >> lastPartLen); // clear before writing
destination[dstBytePtr] |= (byte)(src << firstPartLen); // write first half
dstBytePtr++;
// write first part of next byte
destination[dstBytePtr] &= (byte)(255 << firstPartLen); // clear before writing
destination[dstBytePtr] |= (byte)(src >> lastPartLen); // write second half
}
return;
}
[CLSCompliant(false)]
#if UNSAFE
public static unsafe uint ReadUInt32(byte[] fromBuffer, int numberOfBits, int readBitOffset)
{
NetException.Assert(((numberOfBits > 0) && (numberOfBits <= 32)), "ReadUInt32() can only read between 1 and 32 bits");
if (numberOfBits == 32 && ((readBitOffset % 8) == 0))
{
fixed (byte* ptr = &(fromBuffer[readBitOffset / 8]))
{
return *(((uint*)ptr));
}
}
#else
public static uint ReadUInt32(byte[] fromBuffer, int numberOfBits, int readBitOffset)
{
NetException.Assert(((numberOfBits > 0) && (numberOfBits <= 32)), "ReadUInt32() can only read between 1 and 32 bits");
#endif
uint returnValue;
if (numberOfBits <= 8)
{
returnValue = ReadByte(fromBuffer, numberOfBits, readBitOffset);
return returnValue;
}
returnValue = ReadByte(fromBuffer, 8, readBitOffset);
numberOfBits -= 8;
readBitOffset += 8;
if (numberOfBits <= 8)
{
returnValue |= (uint)(ReadByte(fromBuffer, numberOfBits, readBitOffset) << 8);
return returnValue;
}
returnValue |= (uint)(ReadByte(fromBuffer, 8, readBitOffset) << 8);
numberOfBits -= 8;
readBitOffset += 8;
if (numberOfBits <= 8)
{
uint r = ReadByte(fromBuffer, numberOfBits, readBitOffset);
r <<= 16;
returnValue |= r;
return returnValue;
}
returnValue |= (uint)(ReadByte(fromBuffer, 8, readBitOffset) << 16);
numberOfBits -= 8;
readBitOffset += 8;
returnValue |= (uint)(ReadByte(fromBuffer, numberOfBits, readBitOffset) << 24);
#if BIGENDIAN
// reorder bytes
return
((a & 0xff000000) >> 24) |
((a & 0x00ff0000) >> 8) |
((a & 0x0000ff00) << 8) |
((a & 0x000000ff) << 24);
#endif
return returnValue;
}
//[CLSCompliant(false)]
//public static ulong ReadUInt64(byte[] fromBuffer, int numberOfBits, int readBitOffset)
[CLSCompliant(false)]
public static int WriteUInt32(uint source, int numberOfBits, byte[] destination, int destinationBitOffset)
{
#if BIGENDIAN
// reorder bytes
source = ((source & 0xff000000) >> 24) |
((source & 0x00ff0000) >> 8) |
((source & 0x0000ff00) << 8) |
((source & 0x000000ff) << 24);
#endif
int returnValue = destinationBitOffset + numberOfBits;
if (numberOfBits <= 8)
{
NetBitWriter.WriteByte((byte)source, numberOfBits, destination, destinationBitOffset);
return returnValue;
}
NetBitWriter.WriteByte((byte)source, 8, destination, destinationBitOffset);
destinationBitOffset += 8;
numberOfBits -= 8;
if (numberOfBits <= 8)
{
NetBitWriter.WriteByte((byte)(source >> 8), numberOfBits, destination, destinationBitOffset);
return returnValue;
}
NetBitWriter.WriteByte((byte)(source >> 8), 8, destination, destinationBitOffset);
destinationBitOffset += 8;
numberOfBits -= 8;
if (numberOfBits <= 8)
{
NetBitWriter.WriteByte((byte)(source >> 16), numberOfBits, destination, destinationBitOffset);
return returnValue;
}
NetBitWriter.WriteByte((byte)(source >> 16), 8, destination, destinationBitOffset);
destinationBitOffset += 8;
numberOfBits -= 8;
NetBitWriter.WriteByte((byte)(source >> 24), numberOfBits, destination, destinationBitOffset);
return returnValue;
}
[CLSCompliant(false)]
public static int WriteUInt64(ulong source, int numberOfBits, byte[] destination, int destinationBitOffset)
{
#if BIGENDIAN
source = ((source & 0xff00000000000000L) >> 56) |
((source & 0x00ff000000000000L) >> 40) |
((source & 0x0000ff0000000000L) >> 24) |
((source & 0x000000ff00000000L) >> 8) |
((source & 0x00000000ff000000L) << 8) |
((source & 0x0000000000ff0000L) << 24) |
((source & 0x000000000000ff00L) << 40) |
((source & 0x00000000000000ffL) << 56);
#endif
int returnValue = destinationBitOffset + numberOfBits;
if (numberOfBits <= 8)
{
NetBitWriter.WriteByte((byte)source, numberOfBits, destination, destinationBitOffset);
return returnValue;
}
NetBitWriter.WriteByte((byte)source, 8, destination, destinationBitOffset);
destinationBitOffset += 8;
numberOfBits -= 8;
if (numberOfBits <= 8)
{
NetBitWriter.WriteByte((byte)(source >> 8), numberOfBits, destination, destinationBitOffset);
return returnValue;
}
NetBitWriter.WriteByte((byte)(source >> 8), 8, destination, destinationBitOffset);
destinationBitOffset += 8;
numberOfBits -= 8;
if (numberOfBits <= 8)
{
NetBitWriter.WriteByte((byte)(source >> 16), numberOfBits, destination, destinationBitOffset);
return returnValue;
}
NetBitWriter.WriteByte((byte)(source >> 16), 8, destination, destinationBitOffset);
destinationBitOffset += 8;
numberOfBits -= 8;
if (numberOfBits <= 8)
{
NetBitWriter.WriteByte((byte)(source >> 24), numberOfBits, destination, destinationBitOffset);
return returnValue;
}
NetBitWriter.WriteByte((byte)(source >> 24), 8, destination, destinationBitOffset);
destinationBitOffset += 8;
numberOfBits -= 8;
if (numberOfBits <= 8)
{
NetBitWriter.WriteByte((byte)(source >> 32), numberOfBits, destination, destinationBitOffset);
return returnValue;
}
NetBitWriter.WriteByte((byte)(source >> 32), 8, destination, destinationBitOffset);
destinationBitOffset += 8;
numberOfBits -= 8;
if (numberOfBits <= 8)
{
NetBitWriter.WriteByte((byte)(source >> 40), numberOfBits, destination, destinationBitOffset);
return returnValue;
}
NetBitWriter.WriteByte((byte)(source >> 40), 8, destination, destinationBitOffset);
destinationBitOffset += 8;
numberOfBits -= 8;
if (numberOfBits <= 8)
{
NetBitWriter.WriteByte((byte)(source >> 48), numberOfBits, destination, destinationBitOffset);
return returnValue;
}
NetBitWriter.WriteByte((byte)(source >> 48), 8, destination, destinationBitOffset);
destinationBitOffset += 8;
numberOfBits -= 8;
if (numberOfBits <= 8)
{
NetBitWriter.WriteByte((byte)(source >> 56), numberOfBits, destination, destinationBitOffset);
return returnValue;
}
NetBitWriter.WriteByte((byte)(source >> 56), 8, destination, destinationBitOffset);
destinationBitOffset += 8;
numberOfBits -= 8;
return returnValue;
}
//
// Variable size
//
/// <summary>
/// Write Base128 encoded variable sized unsigned integer
/// </summary>
/// <returns>number of bytes written</returns>
[CLSCompliant(false)]
public static int WriteVariableUInt32(byte[] intoBuffer, int offset, uint value)
{
int retval = 0;
uint num1 = (uint)value;
while (num1 >= 0x80)
{
intoBuffer[offset + retval] = (byte)(num1 | 0x80);
num1 = num1 >> 7;
retval++;
}
intoBuffer[offset + retval] = (byte)num1;
return retval + 1;
}
/// <summary>
/// Reads a UInt32 written using WriteUnsignedVarInt(); will increment offset!
/// </summary>
[CLSCompliant(false)]
public static uint ReadVariableUInt32(byte[] buffer, ref int offset)
{
int num1 = 0;
int num2 = 0;
while (true)
{
if (num2 == 0x23)
throw new FormatException("Bad 7-bit encoded integer");
byte num3 = buffer[offset++];
num1 |= (num3 & 0x7f) << (num2 & 0x1f);
num2 += 7;
if ((num3 & 0x80) == 0)
return (uint)num1;
}
}
}
}

View File

@@ -0,0 +1,126 @@
/* Copyright (c) 2010 Michael Lidgren
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.
*/
using System;
using System.Net;
namespace Lidgren.Network
{
/// <summary>
/// Specialized version of NetPeer used for a "client" connection. It does not accept any incoming connections and maintains a ServerConnection property
/// </summary>
public class NetClient : NetPeer
{
/// <summary>
/// Gets the connection to the server, if any
/// </summary>
public NetConnection ServerConnection
{
get
{
NetConnection retval = null;
if (m_connections.Count > 0)
{
try
{
retval = m_connections[0];
}
catch
{
// preempted!
return null;
}
}
return retval;
}
}
public NetClient(NetPeerConfiguration config)
: base(config)
{
config.AcceptIncomingConnections = false;
}
public override NetConnection Connect(IPEndPoint remoteEndpoint, NetOutgoingMessage hailMessage)
{
lock (m_connections)
{
if (m_connections.Count > 0)
{
LogWarning("Connect attempt failed; Already connected");
return null;
}
}
return base.Connect(remoteEndpoint, hailMessage);
}
/// <summary>
/// Disconnect from server
/// </summary>
/// <param name="byeMessage">reason for disconnect</param>
public void Disconnect(string byeMessage)
{
NetConnection serverConnection = ServerConnection;
if (serverConnection == null)
{
LogWarning("Disconnect requested when not connected!");
return;
}
serverConnection.Disconnect(byeMessage);
}
/// <summary>
/// Sends message to server
/// </summary>
public NetSendResult SendMessage(NetOutgoingMessage msg, NetDeliveryMethod method)
{
NetConnection serverConnection = ServerConnection;
if (serverConnection == null)
{
LogWarning("Cannot send message, no server connection!");
return NetSendResult.Failed;
}
return serverConnection.SendMessage(msg, method, 0);
}
/// <summary>
/// Sends message to server
/// </summary>
public NetSendResult SendMessage(NetOutgoingMessage msg, NetDeliveryMethod method, int sequenceChannel)
{
NetConnection serverConnection = ServerConnection;
if (serverConnection == null)
{
LogWarning("Cannot send message, no server connection!");
return NetSendResult.Failed;
}
return serverConnection.SendMessage(msg, method, sequenceChannel);
}
/// <summary>
/// Returns a string that represents this object
/// </summary>
public override string ToString()
{
return "[NetClient " + ServerConnection + "]";
}
}
}

View File

@@ -0,0 +1,364 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Lidgren.Network
{
public partial class NetConnection
{
internal bool m_connectRequested;
internal bool m_disconnectRequested;
internal bool m_connectionInitiator;
internal string m_disconnectMessage;
internal NetIncomingMessage m_remoteHailMessage;
/// <summary>
/// The message that the remote part specified via Connect() or Approve() - can be null.
/// </summary>
public NetIncomingMessage RemoteHailMessage { get { return m_remoteHailMessage; } }
internal void UnconnectedHeartbeat(float now)
{
m_peer.VerifyNetworkThread();
if (m_disconnectRequested)
ExecuteDisconnect(m_disconnectMessage, true);
if (m_connectRequested)
{
switch (m_status)
{
case NetConnectionStatus.Connected:
case NetConnectionStatus.RespondedConnect:
// reconnect
ExecuteDisconnect("Reconnecting", true);
break;
case NetConnectionStatus.InitiatedConnect:
// send another connect attempt
SendConnect();
break;
case NetConnectionStatus.Disconnected:
throw new NetException("This connection is Disconnected; spent. A new one should have been created");
case NetConnectionStatus.Disconnecting:
// let disconnect finish first
return;
case NetConnectionStatus.None:
default:
SendConnect();
break;
}
return;
}
// TODO: handle dangling connections
}
internal void ExecuteDisconnect(string reason, bool sendByeMessage)
{
m_peer.VerifyNetworkThread();
// m_peer.LogDebug("Executing disconnect");
// clear send queues
for (int i = 0; i < m_sendChannels.Length; i++)
{
NetSenderChannelBase channel = m_sendChannels[i];
if (channel != null)
channel.Reset();
}
if (sendByeMessage)
SendDisconnect(reason, true);
SetStatus(NetConnectionStatus.Disconnected, reason);
m_disconnectRequested = false;
m_connectRequested = false;
}
internal void SendConnect()
{
NetOutgoingMessage om = m_peer.CreateMessage(m_peerConfiguration.AppIdentifier.Length + 1 + 4);
om.m_messageType = NetMessageType.Connect;
om.Write(m_peerConfiguration.AppIdentifier);
om.Write(m_peer.m_uniqueIdentifier);
WriteLocalHail(om);
m_peer.SendLibrary(om, m_remoteEndpoint);
SetStatus(NetConnectionStatus.InitiatedConnect, "Locally requested connect");
m_connectRequested = false;
}
internal void SendConnectResponse(bool onLibraryThread)
{
NetOutgoingMessage om = m_peer.CreateMessage(m_peerConfiguration.AppIdentifier.Length + 1 + 4);
om.m_messageType = NetMessageType.ConnectResponse;
om.Write(m_peerConfiguration.AppIdentifier);
om.Write(m_peer.m_uniqueIdentifier);
WriteLocalHail(om);
if (onLibraryThread)
m_peer.SendLibrary(om, m_remoteEndpoint);
else
m_peer.m_unsentUnconnectedMessages.Enqueue(new NetTuple<System.Net.IPEndPoint, NetOutgoingMessage>(m_remoteEndpoint, om));
SetStatus(NetConnectionStatus.RespondedConnect, "Remotely requested connect");
}
internal void SendDisconnect(string reason, bool onLibraryThread)
{
NetOutgoingMessage om = m_peer.CreateMessage(reason);
om.m_messageType = NetMessageType.Disconnect;
if (onLibraryThread)
m_peer.SendLibrary(om, m_remoteEndpoint);
else
m_peer.m_unsentUnconnectedMessages.Enqueue(new NetTuple<System.Net.IPEndPoint, NetOutgoingMessage>(m_remoteEndpoint, om));
}
private void WriteLocalHail(NetOutgoingMessage om)
{
if (m_localHailMessage != null)
{
byte[] hi = m_localHailMessage.PeekDataBuffer();
if (hi != null && hi.Length >= m_localHailMessage.LengthBytes)
{
if (om.LengthBytes + m_localHailMessage.LengthBytes > m_peerConfiguration.m_maximumTransmissionUnit - 10)
throw new NetException("Hail message too large; can maximally be " + (m_peerConfiguration.m_maximumTransmissionUnit - 10 - om.LengthBytes));
om.Write(m_localHailMessage.PeekDataBuffer(), 0, m_localHailMessage.LengthBytes);
}
}
}
internal void SendConnectionEstablished()
{
NetOutgoingMessage om = m_peer.CreateMessage(0);
om.m_messageType = NetMessageType.ConnectionEstablished;
m_peer.SendLibrary(om, m_remoteEndpoint);
m_sentPingTime = (float)NetTime.Now - (m_peerConfiguration.PingInterval / 2.0f); // delay ping for a little while
if (m_status != NetConnectionStatus.Connected)
SetStatus(NetConnectionStatus.Connected, "Connected to " + NetUtility.ToHexString(m_remoteUniqueIdentifier));
}
/// <summary>
/// Approves this connection; sending a connection response to the remote host
/// </summary>
public void Approve()
{
m_localHailMessage = null;
SendConnectResponse(false);
}
/// <summary>
/// Approves this connection; sending a connection response to the remote host
/// </summary>
/// <param name="localHail">The local hail message that will be set as RemoteHailMessage on the remote host</param>
public void Approve(NetOutgoingMessage localHail)
{
m_localHailMessage = localHail;
SendConnectResponse(false);
}
/// <summary>
/// Denies this connection; disconnecting it
/// </summary>
public void Deny()
{
Deny("");
}
/// <summary>
/// Denies this connection; disconnecting it
/// </summary>
/// <param name="reason">The stated reason for the disconnect, readable as a string in the StatusChanged message on the remote host</param>
public void Deny(string reason)
{
// send disconnect; remove from handshakes
SendDisconnect(reason, false);
// remove from handshakes
m_peer.m_handshakes.Remove(m_remoteEndpoint); // TODO: make this more thread safe? we're on user thread
}
internal void ReceivedHandshake(NetMessageType tp, int ptr, int payloadLength)
{
m_peer.VerifyNetworkThread();
byte[] hail;
switch (tp)
{
case NetMessageType.Connect:
if (m_status == NetConnectionStatus.None)
{
// Whee! Server full has already been checked
bool ok = ValidateHandshakeData(ptr, payloadLength, out hail);
if (ok)
{
if (hail != null)
{
m_remoteHailMessage = m_peer.CreateIncomingMessage(NetIncomingMessageType.Data, hail);
m_remoteHailMessage.LengthBits = (hail.Length * 8);
}
else
{
m_remoteHailMessage = null;
}
if (m_peerConfiguration.IsMessageTypeEnabled(NetIncomingMessageType.ConnectionApproval))
{
// ok, let's not add connection just yet
NetIncomingMessage appMsg = m_peer.CreateIncomingMessage(NetIncomingMessageType.ConnectionApproval, m_remoteHailMessage.LengthBytes);
appMsg.m_senderConnection = this;
appMsg.m_senderEndpoint = this.m_remoteEndpoint;
appMsg.Write(m_remoteHailMessage.m_data, 0, m_remoteHailMessage.LengthBytes);
m_peer.ReleaseMessage(appMsg);
return;
}
SendConnectResponse(true);
}
return;
}
if (m_status == NetConnectionStatus.RespondedConnect)
{
// our ConnectResponse must have been lost
SendConnectResponse(true);
return;
}
m_peer.LogDebug("Unhandled Connect: " + tp + ", status is " + m_status + " length: " + payloadLength);
break;
case NetMessageType.ConnectResponse:
switch (m_status)
{
case NetConnectionStatus.InitiatedConnect:
// awesome
bool ok = ValidateHandshakeData(ptr, payloadLength, out hail);
if (ok)
{
if (hail != null)
{
m_remoteHailMessage = m_peer.CreateIncomingMessage(NetIncomingMessageType.Data, hail);
m_remoteHailMessage.LengthBits = (hail.Length * 8);
}
else
{
m_remoteHailMessage = null;
}
m_peer.AcceptConnection(this);
SendConnectionEstablished();
return;
}
break;
case NetConnectionStatus.RespondedConnect:
// hello, wtf?
break;
case NetConnectionStatus.Disconnecting:
case NetConnectionStatus.Disconnected:
case NetConnectionStatus.None:
// wtf? anyway, bye!
break;
case NetConnectionStatus.Connected:
// my ConnectionEstablished must have been lost, send another one
SendConnectionEstablished();
return;
}
break;
case NetMessageType.ConnectionEstablished:
switch (m_status)
{
case NetConnectionStatus.Connected:
// ok...
break;
case NetConnectionStatus.Disconnected:
case NetConnectionStatus.Disconnecting:
case NetConnectionStatus.None:
// too bad, almost made it
break;
case NetConnectionStatus.InitiatedConnect:
// weird, should have been ConnectResponse...
break;
case NetConnectionStatus.RespondedConnect:
// awesome
m_peer.AcceptConnection(this);
m_sentPingTime = (float)NetTime.Now - (m_peerConfiguration.PingInterval / 2.0f); // delay ping for a little while
SetStatus(NetConnectionStatus.Connected, "Connected to " + NetUtility.ToHexString(m_remoteUniqueIdentifier));
return;
}
break;
case NetMessageType.Disconnect:
// ouch
string reason = "Ouch";
try
{
NetIncomingMessage inc = m_peer.SetupReadHelperMessage(ptr, payloadLength);
reason = inc.ReadString();
}
catch
{
}
SetStatus(NetConnectionStatus.Disconnected, reason);
break;
default:
m_peer.LogDebug("Unhandled type during handshake: " + tp + " length: " + payloadLength);
break;
}
}
private bool ValidateHandshakeData(int ptr, int payloadLength, out byte[] hail)
{
hail = null;
// create temporary incoming message
NetIncomingMessage msg = m_peer.SetupReadHelperMessage(ptr, payloadLength);
try
{
string remoteAppIdentifier = msg.ReadString();
long remoteUniqueIdentifier = msg.ReadInt64();
int remainingBytes = payloadLength - (msg.PositionInBytes - ptr);
if (remainingBytes > 0)
hail = msg.ReadBytes(remainingBytes);
if (remoteAppIdentifier != m_peer.m_configuration.AppIdentifier)
{
// wrong app identifier
ExecuteDisconnect("Wrong application identifier!", true);
return false;
}
m_remoteUniqueIdentifier = remoteUniqueIdentifier;
}
catch(Exception ex)
{
// whatever; we failed
ExecuteDisconnect("Handshake data validation failed", true);
m_peer.LogWarning("ReadRemoteHandshakeData failed: " + ex.Message);
return false;
}
return true;
}
/// <summary>
/// Disconnect from the remote peer
/// </summary>
/// <param name="byeMessage">the message to send with the disconnect message</param>
public void Disconnect(string byeMessage)
{
// user or library thread
if (m_status == NetConnectionStatus.None || m_status == NetConnectionStatus.Disconnected)
return;
m_peer.LogVerbose("Disconnect requested for " + this);
m_disconnectMessage = byeMessage;
if (m_status != NetConnectionStatus.Disconnected && m_status != NetConnectionStatus.None)
SetStatus(NetConnectionStatus.Disconnecting, byeMessage);
m_disconnectRequested = true;
}
}
}

View File

@@ -0,0 +1,83 @@
using System;
namespace Lidgren.Network
{
public partial class NetConnection
{
private float m_sentPingTime;
private int m_sentPingNumber;
private float m_averageRoundtripTime;
private float m_timeoutDeadline = float.MaxValue;
/// <summary>
/// Gets the current average roundtrip time in seconds
/// </summary>
public float AverageRoundtripTime { get { return m_averageRoundtripTime; } }
internal void SendPing()
{
m_peer.VerifyNetworkThread();
m_sentPingNumber++;
if (m_sentPingNumber >= 256)
m_sentPingNumber = 0;
m_sentPingTime = (float)NetTime.Now;
NetOutgoingMessage om = m_peer.CreateMessage(1);
om.Write((byte)m_sentPingNumber);
om.m_messageType = NetMessageType.Ping;
int len = om.Encode(m_peer.m_sendBuffer, 0, 0);
bool connectionReset;
m_peer.SendPacket(len, m_remoteEndpoint, 1, out connectionReset);
}
internal void SendPong(int pingNumber)
{
m_peer.VerifyNetworkThread();
NetOutgoingMessage om = m_peer.CreateMessage(1);
om.Write((byte)pingNumber);
om.m_messageType = NetMessageType.Pong;
int len = om.Encode(m_peer.m_sendBuffer, 0, 0);
bool connectionReset;
m_peer.SendPacket(len, m_remoteEndpoint, 1, out connectionReset);
}
internal void ReceivedPong(float now, int pongNumber)
{
if (pongNumber != m_sentPingNumber)
{
m_peer.LogVerbose("Ping/Pong mismatch; dropped message?");
return;
}
m_timeoutDeadline = now + m_peerConfiguration.m_connectionTimeout;
float rtt = now - m_sentPingTime;
NetException.Assert(rtt >= 0);
if (m_averageRoundtripTime < 0)
{
m_averageRoundtripTime = rtt; // initial estimate
m_peer.LogDebug("Initiated average roundtrip time to " + NetTime.ToReadable(m_averageRoundtripTime));
}
else
{
m_averageRoundtripTime = (m_averageRoundtripTime * 0.7f) + (float)(rtt * 0.3f);
m_peer.LogVerbose("Updated average roundtrip time to " + NetTime.ToReadable(m_averageRoundtripTime));
}
// update resend delay for all channels
float resendDelay = GetResendDelay();
foreach (var chan in m_sendChannels)
{
var rchan = chan as NetReliableSenderChannel;
if (rchan != null)
rchan.m_resendDelay = resendDelay;
}
m_peer.LogVerbose("Timeout deadline pushed to " + m_timeoutDeadline);
}
}
}

View File

@@ -0,0 +1,309 @@
/* Copyright (c) 2010 Michael Lidgren
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.
*/
using System;
using System.Collections.Generic;
using System.Threading;
namespace Lidgren.Network
{
public partial class NetConnection
{
private readonly int[] m_nextSendSequenceNumber;
private readonly ushort[] m_lastReceivedSequenced;
internal readonly List<NetSending> m_unackedSends = new List<NetSending>();
//internal readonly Dictionary<ushort, NetOutgoingMessage>[] m_storedMessages = new Dictionary<ushort, NetOutgoingMessage>[NetConstants.NumReliableChannels];
//internal readonly Dictionary<NetOutgoingMessage, ushort>[] m_inverseStored = new Dictionary<NetOutgoingMessage, ushort>[NetConstants.NumReliableChannels];
internal readonly NetBitVector m_storedMessagesNotEmpty = new NetBitVector(NetConstants.NumReliableChannels);
private readonly ushort[] m_nextExpectedReliableSequence = new ushort[NetConstants.NumReliableChannels];
private readonly List<NetIncomingMessage>[] m_withheldMessages = new List<NetIncomingMessage>[NetConstants.NetChannelsPerDeliveryMethod]; // only for ReliableOrdered
internal readonly Queue<int> m_acknowledgesToSend = new Queue<int>();
internal double m_nextForceAckTime;
private readonly NetBitVector[] m_reliableReceived = new NetBitVector[NetConstants.NumSequenceNumbers];
public int GetStoredMessagesCount()
{
return m_unackedSends.Count;
}
public int GetWithheldMessagesCount()
{
int retval = 0;
for (int i = 0; i < m_withheldMessages.Length; i++)
{
var list = m_withheldMessages[i];
if (list != null)
retval += list.Count;
}
return retval;
}
internal ushort GetSendSequenceNumber(NetMessageType mtp)
{
if (mtp < NetMessageType.UserSequenced)
return 0;
int slot = (int)mtp - (int)NetMessageType.UserSequenced;
int retval;
lock (m_nextSendSequenceNumber)
{
retval = m_nextSendSequenceNumber[slot];
if (retval == ushort.MaxValue)
retval = -1;
m_nextSendSequenceNumber[slot] = retval + 1;
}
return (ushort)retval;
}
internal static int Relate(int seqNr, int lastReceived)
{
return (seqNr < lastReceived ? (seqNr + NetConstants.NumSequenceNumbers) - lastReceived : seqNr - lastReceived);
}
// returns true if message should be rejected
internal bool ReceivedSequencedMessage(NetMessageType mtp, ushort seqNr)
{
int slot = (int)mtp - (int)NetMessageType.UserSequenced;
int diff = Relate(seqNr, m_lastReceivedSequenced[slot]);
if (diff == 0)
return true; // reject; already received
if (diff > (ushort.MaxValue / 2))
return true; // reject; out of window
m_lastReceivedSequenced[slot] = seqNr;
return false;
}
/*
// called by Encode() to retrieve a sequence number and store the message for potential resending
internal ushort StoreReliableMessage(double now, NetOutgoingMessage msg)
{
m_owner.VerifyNetworkThread();
int seqNr = -1;
int reliableSlot = (int)msg.m_type - (int)NetMessageType.UserReliableUnordered;
Dictionary<ushort, NetOutgoingMessage> slotDict = m_storedMessages[reliableSlot];
Dictionary<NetOutgoingMessage, ushort> invSlotDict = m_inverseStored[reliableSlot];
if (slotDict == null)
{
slotDict = new Dictionary<ushort, NetOutgoingMessage>();
m_storedMessages[reliableSlot] = slotDict;
invSlotDict = new Dictionary<NetOutgoingMessage, ushort>();
m_inverseStored[reliableSlot] = invSlotDict;
// (cannot be a resend here)
}
else
{
// we assume there's a invSlotDict if there's a slotDict
// is it a resend? if so, return the old sequence number
ushort oldSeqNr;
if (invSlotDict.TryGetValue(msg, out oldSeqNr))
seqNr = oldSeqNr;
}
if (seqNr != -1)
{
// resend!
// m_owner.LogDebug("Resending " + msg.m_type + "|" + seqNr);
m_statistics.MessageResent();
}
else
{
// first send
seqNr = GetSendSequenceNumber(msg.m_type);
//m_owner.LogDebug("Sending " + msg.m_type + "|" + seqNr);
Interlocked.Increment(ref msg.m_inQueueCount);
slotDict.Add((ushort)seqNr, msg);
invSlotDict.Add(msg, (ushort)seqNr);
if (slotDict.Count > 0)
m_storedMessagesNotEmpty.Set(reliableSlot, true);
}
// schedule next resend
int numSends = msg.m_numSends;
float[] baseTimes = m_peerConfiguration.m_resendBaseTime;
float[] multiplers = m_peerConfiguration.m_resendRTTMultiplier;
if (numSends >= baseTimes.Length)
numSends = baseTimes.Length - 1;
msg.m_nextResendTime = now + baseTimes[numSends] + (m_averageRoundtripTime * multiplers[numSends]);
return (ushort)seqNr;
}
*/
private void HandleIncomingAcks(int ptr, int payloadByteLength)
{
m_owner.VerifyNetworkThread();
int numAcks = payloadByteLength / 3;
if (numAcks * 3 != payloadByteLength)
m_owner.LogWarning("Malformed ack message; payload length is " + payloadByteLength);
byte[] buffer = m_owner.m_receiveBuffer;
for (int i = 0; i < numAcks; i++)
{
ushort seqNr = (ushort)(buffer[ptr++] | (buffer[ptr++] << 8));
NetMessageType tp = (NetMessageType)buffer[ptr++];
//m_owner.LogDebug("Got ack for " + tp + "|" + seqNr);
foreach(NetSending send in m_unackedSends)
{
if (send.MessageType == tp && send.SequenceNumber == seqNr)
{
// found it
m_lastSendRespondedTo = NetTime.Now; // TODO: calculate from send.NextResend and send.NumSends
int unfin = send.Message.m_numUnfinishedSendings;
send.Message.m_numUnfinishedSendings = unfin - 1;
if (unfin <= 0)
m_owner.Recycle(send.Message); // every sent has been acked; free the message
m_owner.LogVerbose("Received ack for " + tp + "#" + seqNr);
m_unackedSends.Remove(send);
// TODO: recycle send
break;
}
}
/*
// remove stored message
int reliableSlot = (int)tp - (int)NetMessageType.UserReliableUnordered;
var dict = m_storedMessages[reliableSlot];
if (dict == null)
continue;
// find message
NetOutgoingMessage om;
if (dict.TryGetValue(seqNr, out om))
{
// found!
dict.Remove(seqNr);
m_inverseStored[reliableSlot].Remove(om);
if (dict.Count < 1)
m_storedMessagesNotEmpty[reliableSlot] = false;
Interlocked.Decrement(ref om.m_inQueueCount);
NetException.Assert(om.m_lastSentTime != 0);
if (om.m_lastSentTime > m_lastSendRespondedTo)
m_lastSendRespondedTo = om.m_lastSentTime;
if (om.m_inQueueCount < 1)
m_owner.Recycle(om);
}
*/
// TODO: receipt handling
}
}
private void ExpectedReliableSequenceArrived(int reliableSlot, bool isFragment)
{
NetBitVector received = m_reliableReceived[reliableSlot];
int nextExpected = m_nextExpectedReliableSequence[reliableSlot];
if (received == null)
{
nextExpected = (nextExpected + 1) % NetConstants.NumSequenceNumbers;
m_nextExpectedReliableSequence[reliableSlot] = (ushort)nextExpected;
return;
}
received[(nextExpected + (NetConstants.NumSequenceNumbers / 2)) % NetConstants.NumSequenceNumbers] = false; // reset for next pass
nextExpected = (nextExpected + 1) % NetConstants.NumSequenceNumbers;
//
// Release withheld messages
//
while (received[nextExpected] == true)
{
// it seems we've already received the next expected reliable sequence number
// ordered?
const int orderedSlotsStart = ((int)NetMessageType.UserReliableOrdered - (int)NetMessageType.UserReliableUnordered);
if (reliableSlot >= orderedSlotsStart)
{
// ... then we should have a withheld message waiting
// this should be a withheld message
int orderedSlot = reliableSlot - orderedSlotsStart;
bool foundWithheld = false;
List<NetIncomingMessage> withheldList = m_withheldMessages[orderedSlot];
if (withheldList != null)
{
foreach (NetIncomingMessage wm in withheldList)
{
int wmSeqChan = wm.SequenceChannel;
if (orderedSlot == wmSeqChan && wm.m_sequenceNumber == nextExpected)
{
// Found withheld message due for delivery
m_owner.LogVerbose("Releasing withheld message " + wm);
//Console.WriteLine("Releasing withheld message " + wm);
// Accept, unless a fragment
if (wm.m_fragmentationInfo == null)
m_owner.ReleaseMessage(wm);
foundWithheld = true;
withheldList.Remove(wm);
// advance next expected
received[(nextExpected + (NetConstants.NumSequenceNumbers / 2)) % NetConstants.NumSequenceNumbers] = false; // reset for next pass
nextExpected = (nextExpected + 1) % NetConstants.NumSequenceNumbers;
break;
}
}
}
if (!foundWithheld)
{
// probably a fragment; we don't withhold those - advance anyway
//Console.WriteLine("Withheld #" + nextExpected + " not found; probably a fragment!");
//received[(nextExpected + (NetConstants.NumSequenceNumbers / 2)) % NetConstants.NumSequenceNumbers] = false; // reset for next pass
//nextExpected = (nextExpected + 1) % NetConstants.NumSequenceNumbers;
throw new NetException("Withheld message not found!");
}
}
}
m_nextExpectedReliableSequence[reliableSlot] = (ushort)nextExpected;
}
}
}

View File

@@ -0,0 +1,443 @@
using System;
using System.Net;
using System.Threading;
using System.Diagnostics;
namespace Lidgren.Network
{
/// <summary>
/// Represents a connection to a remote peer
/// </summary>
[DebuggerDisplay("RemoteUniqueIdentifier={RemoteUniqueIdentifier} RemoteEndpoint={RemoteEndpoint}")]
public partial class NetConnection
{
internal NetPeer m_peer;
internal NetPeerConfiguration m_peerConfiguration;
internal NetConnectionStatus m_status;
internal NetConnectionStatus m_visibleStatus;
internal IPEndPoint m_remoteEndpoint;
internal NetSenderChannelBase[] m_sendChannels;
internal NetReceiverChannelBase[] m_receiveChannels;
internal NetOutgoingMessage m_localHailMessage;
internal long m_remoteUniqueIdentifier;
internal NetQueue<NetTuple<NetMessageType, int>> m_queuedAcks;
private int m_sendBufferWritePtr;
private int m_sendBufferNumMessages;
private object m_tag;
internal NetConnectionStatistics m_statistics;
/// <summary>
/// Gets or sets the application defined object containing data about the connection
/// </summary>
public object Tag
{
get { return m_tag; }
set { m_tag = value; }
}
/// <summary>
/// Gets the peer which holds this connection
/// </summary>
public NetPeer Peer { get { return m_peer; } }
/// <summary>
/// Gets the current status of the connection (synced to the last status message read)
/// </summary>
public NetConnectionStatus Status { get { return m_visibleStatus; } }
/// <summary>
/// Gets various statistics for this connection
/// </summary>
public NetConnectionStatistics Statistics { get { return m_statistics; } }
/// <summary>
/// Gets the remote endpoint for the connection
/// </summary>
public IPEndPoint RemoteEndpoint { get { return m_remoteEndpoint; } }
/// <summary>
/// Gets the unique identifier of the remote NetPeer for this connection
/// </summary>
public long RemoteUniqueIdentifier { get { return m_remoteUniqueIdentifier; } }
// gets the time before automatically resending an unacked message
internal float GetResendDelay()
{
float avgRtt = m_averageRoundtripTime;
if (avgRtt <= 0)
avgRtt = 0.1f; // "default" resend is based on 100 ms roundtrip time
return 0.01f + (avgRtt * 2); // 10 ms + double rtt
}
internal NetConnection(NetPeer peer, IPEndPoint remoteEndpoint)
{
m_peer = peer;
m_peerConfiguration = m_peer.Configuration;
m_status = NetConnectionStatus.None;
m_visibleStatus = NetConnectionStatus.None;
m_remoteEndpoint = remoteEndpoint;
m_sendChannels = new NetSenderChannelBase[NetConstants.NumTotalChannels];
m_receiveChannels = new NetReceiverChannelBase[NetConstants.NumTotalChannels];
m_queuedAcks = new NetQueue<NetTuple<NetMessageType, int>>(4);
m_statistics = new NetConnectionStatistics(this);
m_averageRoundtripTime = -1.0f;
}
internal void SetStatus(NetConnectionStatus status, string reason)
{
// user or library thread
if (status == m_status)
return;
m_status = status;
if (reason == null)
reason = string.Empty;
if (m_status == NetConnectionStatus.Connected)
{
m_timeoutDeadline = (float)NetTime.Now + m_peerConfiguration.m_connectionTimeout;
m_peer.LogVerbose("Timeout deadline initialized to " + m_timeoutDeadline);
}
if (m_peerConfiguration.IsMessageTypeEnabled(NetIncomingMessageType.StatusChanged))
{
NetIncomingMessage info = m_peer.CreateIncomingMessage(NetIncomingMessageType.StatusChanged, 4 + reason.Length + (reason.Length > 126 ? 2 : 1));
info.m_senderConnection = this;
info.m_senderEndpoint = m_remoteEndpoint;
info.Write((byte)m_status);
info.Write(reason);
m_peer.ReleaseMessage(info);
}
else
{
// app dont want those messages, update visible status immediately
m_visibleStatus = m_status;
}
}
internal void Heartbeat(float now, uint frameCounter)
{
m_peer.VerifyNetworkThread();
NetException.Assert(m_status != NetConnectionStatus.InitiatedConnect && m_status != NetConnectionStatus.RespondedConnect);
if ((frameCounter % 5) == 0)
{
if (now > m_timeoutDeadline)
{
//
// connection timed out
//
m_peer.LogVerbose("Connection timed out at " + now + " deadline was " + m_timeoutDeadline);
ExecuteDisconnect("Connection timed out", true);
}
// send ping?
if (m_status == NetConnectionStatus.Connected)
{
if (now > m_sentPingTime + m_peer.m_configuration.m_pingInterval)
SendPing();
}
}
bool connectionReset; // TODO: handle connection reset
//
// Note: at this point m_sendBufferWritePtr and m_sendBufferNumMessages may be non-null; resends may already be queued up
//
byte[] sendBuffer = m_peer.m_sendBuffer;
int mtu = m_peerConfiguration.m_maximumTransmissionUnit;
if ((frameCounter % 3) == 0) // coalesce a few frames
{
//
// send ack messages
//
while (m_queuedAcks.Count > 0)
{
int acks = (mtu - (m_sendBufferWritePtr + 5)) / 3; // 3 bytes per actual ack
if (acks > m_queuedAcks.Count)
acks = m_queuedAcks.Count;
NetException.Assert(acks > 0);
m_sendBufferNumMessages++;
// write acks header
sendBuffer[m_sendBufferWritePtr++] = (byte)NetMessageType.Acknowledge;
sendBuffer[m_sendBufferWritePtr++] = 0; // no sequence number
sendBuffer[m_sendBufferWritePtr++] = 0; // no sequence number
int len = (acks * 3) * 8; // bits
sendBuffer[m_sendBufferWritePtr++] = (byte)len;
sendBuffer[m_sendBufferWritePtr++] = (byte)(len >> 8);
// write acks
for (int i = 0; i < acks; i++)
{
NetTuple<NetMessageType, int> tuple;
m_queuedAcks.TryDequeue(out tuple);
//m_peer.LogVerbose("Sending ack for " + tuple.Item1 + "#" + tuple.Item2);
sendBuffer[m_sendBufferWritePtr++] = (byte)tuple.Item1;
sendBuffer[m_sendBufferWritePtr++] = (byte)tuple.Item2;
sendBuffer[m_sendBufferWritePtr++] = (byte)(tuple.Item2 >> 8);
}
if (m_queuedAcks.Count > 0)
{
// send packet and go for another round of acks
NetException.Assert(m_sendBufferWritePtr > 0 && m_sendBufferNumMessages > 0);
m_peer.SendPacket(m_sendBufferWritePtr, m_remoteEndpoint, m_sendBufferNumMessages, out connectionReset);
m_statistics.PacketSent(m_sendBufferWritePtr, 1);
m_sendBufferWritePtr = 0;
m_sendBufferNumMessages = 0;
}
}
}
//
// send queued messages
//
foreach (NetSenderChannelBase channel in m_sendChannels)
{
NetException.Assert(m_sendBufferWritePtr < 1 || m_sendBufferNumMessages > 0);
if (channel != null)
channel.SendQueuedMessages(now);
NetException.Assert(m_sendBufferWritePtr < 1 || m_sendBufferNumMessages > 0);
}
//
// Put on wire data has been written to send buffer but not yet sent
//
if (m_sendBufferWritePtr > 0)
{
m_peer.VerifyNetworkThread();
NetException.Assert(m_sendBufferWritePtr > 0 && m_sendBufferNumMessages > 0);
m_peer.SendPacket(m_sendBufferWritePtr, m_remoteEndpoint, m_sendBufferNumMessages, out connectionReset);
m_statistics.PacketSent(m_sendBufferWritePtr, m_sendBufferNumMessages);
m_sendBufferWritePtr = 0;
m_sendBufferNumMessages = 0;
}
}
// Queue an item for immediate sending on the wire
// This method is called from the ISenderChannels
internal void QueueSendMessage(NetOutgoingMessage om, int seqNr)
{
m_peer.VerifyNetworkThread();
int sz = om.GetEncodedSize();
if (sz > m_peerConfiguration.m_maximumTransmissionUnit)
m_peer.LogWarning("Message larger than MTU! Fragmentation must have failed!");
if (m_sendBufferWritePtr + sz > m_peerConfiguration.m_maximumTransmissionUnit)
{
bool connReset; // TODO: handle connection reset
NetException.Assert(m_sendBufferWritePtr > 0 && m_sendBufferNumMessages > 0); // or else the message should have been fragmented earlier
m_peer.SendPacket(m_sendBufferWritePtr, m_remoteEndpoint, m_sendBufferNumMessages, out connReset);
m_statistics.PacketSent(m_sendBufferWritePtr, m_sendBufferNumMessages);
m_sendBufferWritePtr = 0;
m_sendBufferNumMessages = 0;
}
m_sendBufferWritePtr = om.Encode(m_peer.m_sendBuffer, m_sendBufferWritePtr, seqNr);
m_sendBufferNumMessages++;
NetException.Assert(m_sendBufferWritePtr > 0, "Encoded zero size message?");
NetException.Assert(m_sendBufferNumMessages > 0);
}
/// <summary>
/// Send a message to this remote connection
/// </summary>
/// <param name="msg">The message to send</param>
/// <param name="method">How to deliver the message</param>
/// <param name="sequenceChannel">Sequence channel within the delivery method</param>
public NetSendResult SendMessage(NetOutgoingMessage msg, NetDeliveryMethod method, int sequenceChannel)
{
return m_peer.SendMessage(msg, this, method, sequenceChannel);
}
// called by SendMessage() and NetPeer.SendMessage; ie. may be user thread
internal NetSendResult EnqueueMessage(NetOutgoingMessage msg, NetDeliveryMethod method, int sequenceChannel)
{
NetMessageType tp = (NetMessageType)((int)method + sequenceChannel);
msg.m_messageType = tp;
// TODO: do we need to make this more thread safe?
int channelSlot = (int)method - 1 + sequenceChannel;
NetSenderChannelBase chan = m_sendChannels[channelSlot];
if (chan == null)
chan = CreateSenderChannel(tp);
if (msg.GetEncodedSize() > m_peerConfiguration.m_maximumTransmissionUnit)
throw new NetException("Message too large! Fragmentation failure?");
return chan.Enqueue(msg);
}
// may be on user thread
private NetSenderChannelBase CreateSenderChannel(NetMessageType tp)
{
NetSenderChannelBase chan;
NetDeliveryMethod method = NetUtility.GetDeliveryMethod(tp);
int sequenceChannel = (int)tp - (int)method;
switch (method)
{
case NetDeliveryMethod.Unreliable:
case NetDeliveryMethod.UnreliableSequenced:
chan = new NetUnreliableSenderChannel(this, NetConstants.UnreliableWindowSize);
break;
case NetDeliveryMethod.ReliableOrdered:
chan = new NetReliableSenderChannel(this, NetConstants.ReliableOrderedWindowSize);
break;
case NetDeliveryMethod.ReliableSequenced:
case NetDeliveryMethod.ReliableUnordered:
default:
//
// TODO: this is placeholder!
//
chan = new NetReliableSenderChannel(this, 64);
break;
}
int channelSlot = (int)method - 1 + sequenceChannel;
NetException.Assert(m_sendChannels[channelSlot] == null);
m_sendChannels[channelSlot] = chan;
return chan;
}
// received a library message while Connected
internal void ReceivedLibraryMessage(NetMessageType tp, int ptr, int payloadLength)
{
m_peer.VerifyNetworkThread();
float now = (float)NetTime.Now;
switch (tp)
{
case NetMessageType.Disconnect:
NetIncomingMessage msg = m_peer.SetupReadHelperMessage(ptr, payloadLength);
ExecuteDisconnect(msg.ReadString(), false);
break;
case NetMessageType.Acknowledge:
for (int i = 0; i < payloadLength; i+=3)
{
NetMessageType acktp = (NetMessageType)m_peer.m_receiveBuffer[ptr++]; // netmessagetype
int seqNr = m_peer.m_receiveBuffer[ptr++];
seqNr |= (m_peer.m_receiveBuffer[ptr++] << 8);
NetSenderChannelBase chan = m_sendChannels[(int)acktp - 1];
if (chan == null)
chan = CreateSenderChannel(acktp);
//m_peer.LogVerbose("Received ack for " + acktp + "#" + seqNr);
chan.ReceiveAcknowledge(now, seqNr);
}
break;
case NetMessageType.Ping:
int pingNr = m_peer.m_receiveBuffer[ptr++];
SendPong(pingNr);
break;
case NetMessageType.Pong:
int pongNr = m_peer.m_receiveBuffer[ptr++];
ReceivedPong(now, pongNr);
break;
default:
m_peer.LogWarning("Connection received unhandled library message: " + tp);
break;
}
}
internal void ReceivedMessage(NetIncomingMessage msg)
{
m_peer.VerifyNetworkThread();
NetMessageType tp = msg.m_receivedMessageType;
int channelSlot = (int)tp - 1;
NetReceiverChannelBase chan = m_receiveChannels[channelSlot];
if (chan == null)
chan = CreateReceiverChannel(tp);
chan.ReceiveMessage(msg);
}
private NetReceiverChannelBase CreateReceiverChannel(NetMessageType tp)
{
m_peer.VerifyNetworkThread();
// create receiver channel
NetReceiverChannelBase chan;
NetDeliveryMethod method = NetUtility.GetDeliveryMethod(tp);
switch (method)
{
case NetDeliveryMethod.Unreliable:
chan = new NetUnreliableUnorderedReceiver(this);
break;
case NetDeliveryMethod.ReliableOrdered:
chan = new NetReliableOrderedReceiver(this, NetConstants.ReliableOrderedWindowSize);
break;
case NetDeliveryMethod.UnreliableSequenced:
chan = new NetUnreliableSequencedReceiver(this);
break;
case NetDeliveryMethod.ReliableUnordered:
chan = new NetReliableUnorderedReceiver(this, NetConstants.ReliableOrderedWindowSize);
break;
case NetDeliveryMethod.ReliableSequenced:
chan = new NetReliableSequencedReceiver(this, NetConstants.ReliableSequencedWindowSize);
break;
default:
throw new NetException("Unhandled NetDeliveryMethod!");
}
int channelSlot = (int)tp - 1;
NetException.Assert(m_receiveChannels[channelSlot] == null);
m_receiveChannels[channelSlot] = chan;
return chan;
}
internal void QueueAck(NetMessageType tp, int sequenceNumber)
{
m_queuedAcks.Enqueue(new NetTuple<NetMessageType, int>(tp, sequenceNumber));
}
/// <summary>
/// Zero windowSize indicates that the channel is not yet instantiated (used)
/// Negative freeWindowSlots means this amount of messages are currently queued but delayed due to closed window
/// </summary>
public void GetSendQueueInfo(NetDeliveryMethod method, int sequenceChannel, out int windowSize, out int freeWindowSlots)
{
int channelSlot = (int)method - 1 + sequenceChannel;
var chan = m_sendChannels[channelSlot];
if (chan == null)
{
windowSize = 0;
freeWindowSlots = 0;
return;
}
windowSize = chan.WindowSize;
freeWindowSlots = chan.GetAllowedSends() - chan.m_queuedSends.Count;
return;
}
internal void Shutdown(string reason)
{
ExecuteDisconnect(reason, true);
}
/// <summary>
/// Returns a string that represents this object
/// </summary>
public override string ToString()
{
return "[NetConnection to " + m_remoteEndpoint + "]";
}
}
}

View File

@@ -0,0 +1,158 @@
/* Copyright (c) 2010 Michael Lidgren
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.
*/
using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
namespace Lidgren.Network
{
/// <summary>
/// Statistics for a NetConnection instance
/// </summary>
public sealed class NetConnectionStatistics
{
private readonly NetConnection m_connection;
internal int m_sentPackets;
internal int m_receivedPackets;
internal int m_sentMessages;
internal int m_receivedMessages;
internal int m_sentBytes;
internal int m_receivedBytes;
internal int m_resentMessages;
internal NetConnectionStatistics(NetConnection conn)
{
m_connection = conn;
Reset();
}
internal void Reset()
{
m_sentPackets = 0;
m_receivedPackets = 0;
m_sentBytes = 0;
m_receivedBytes = 0;
}
/// <summary>
/// Gets the number of sent packets for this connection
/// </summary>
public int SentPackets { get { return m_sentPackets; } }
/// <summary>
/// Gets the number of received packets for this connection
/// </summary>
public int ReceivedPackets { get { return m_receivedPackets; } }
/// <summary>
/// Gets the number of sent bytes for this connection
/// </summary>
public int SentBytes { get { return m_sentBytes; } }
/// <summary>
/// Gets the number of received bytes for this connection
/// </summary>
public int ReceivedBytes { get { return m_receivedBytes; } }
/// <summary>
/// Gets the number of resent reliable messages for this connection
/// </summary>
public int ResentMessages { get { return m_resentMessages; } }
// public double LastSendRespondedTo { get { return m_connection.m_lastSendRespondedTo; } }
[Conditional("DEBUG")]
internal void PacketSent(int numBytes, int numMessages)
{
NetException.Assert(numBytes > 0 && numMessages > 0);
m_sentPackets++;
m_sentBytes += numBytes;
m_sentMessages += numMessages;
}
[Conditional("DEBUG")]
internal void PacketReceived(int numBytes, int numMessages)
{
NetException.Assert(numBytes > 0 && numMessages > 0);
m_receivedPackets++;
m_receivedBytes += numBytes;
m_receivedMessages += numMessages;
}
[Conditional("DEBUG")]
internal void MessageResent()
{
m_resentMessages++;
}
/// <summary>
/// Returns a string that represents this object
/// </summary>
public override string ToString()
{
StringBuilder bdr = new StringBuilder();
//bdr.AppendLine("Average roundtrip time: " + NetTime.ToReadable(m_connection.m_averageRoundtripTime));
bdr.AppendLine("Sent " + m_sentBytes + " bytes in " + m_sentMessages + " messages in " + m_sentPackets + " packets");
bdr.AppendLine("Received " + m_receivedBytes + " bytes in " + m_receivedMessages + " messages in " + m_receivedPackets + " packets");
if (m_resentMessages > 0)
bdr.AppendLine("Resent messages: " + m_resentMessages);
int numUnsent = 0;
int numStored = 0;
foreach (NetSenderChannelBase sendChan in m_connection.m_sendChannels)
{
if (sendChan == null)
continue;
numUnsent += sendChan.m_queuedSends.Count;
var relSendChan = sendChan as NetReliableSenderChannel;
if (relSendChan != null)
{
for (int i = 0; i < relSendChan.m_storedMessages.Length; i++)
if (relSendChan.m_storedMessages[i].Message != null)
numStored++;
}
}
int numWithheld = 0;
foreach (NetReceiverChannelBase recChan in m_connection.m_receiveChannels)
{
var relRecChan = recChan as NetReliableOrderedReceiver;
if (relRecChan != null)
{
for (int i = 0; i < relRecChan.m_withheldMessages.Length; i++)
if (relRecChan.m_withheldMessages[i] != null)
numWithheld++;
}
}
bdr.AppendLine("Unsent messages: " + numUnsent);
bdr.AppendLine("Stored messages: " + numStored);
bdr.AppendLine("Withheld messages: " + numWithheld);
return bdr.ToString();
}
}
}

View File

@@ -0,0 +1,58 @@
/* Copyright (c) 2010 Michael Lidgren
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.
*/
using System;
namespace Lidgren.Network
{
/// <summary>
/// Status for a NetConnection instance
/// </summary>
public enum NetConnectionStatus
{
/// <summary>
/// No connection, or attempt, in place
/// </summary>
None,
/// <summary>
/// Connect has been sent; waiting for ConnectResponse
/// </summary>
InitiatedConnect,
/// <summary>
/// Connect was received and ConnectResponse has been sent; waiting for ConnectionEstablished
/// </summary>
RespondedConnect, // we got Connect, sent ConnectResponse
/// <summary>
/// Connected
/// </summary>
Connected, // we received ConnectResponse (if initiator) or ConnectionEstablished (if passive)
/// <summary>
/// In the process of disconnecting
/// </summary>
Disconnecting,
/// <summary>
/// Disconnected
/// </summary>
Disconnected
}
}

View File

@@ -0,0 +1,54 @@
/* Copyright (c) 2010 Michael Lidgren
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.
*/
using System;
namespace Lidgren.Network
{
/// <summary>
/// All the constants used when compiling the library
/// </summary>
internal static class NetConstants
{
internal const int NumTotalChannels = 99;
internal const int NetChannelsPerDeliveryMethod = 32;
internal const int NumSequenceNumbers = 1024;
internal const int HeaderByteSize = 5;
internal const int UnreliableWindowSize = 128;
internal const int ReliableOrderedWindowSize = 64;
internal const int ReliableSequencedWindowSize = 64;
internal const int MaxFragmentationGroups = ushort.MaxValue - 1;
/// <summary>
/// Number of channels which needs a sequence number to work
/// </summary>
internal const int NumSequencedChannels = ((int)NetMessageType.UserReliableOrdered1 + NetConstants.NetChannelsPerDeliveryMethod) - (int)NetMessageType.UserSequenced1;
/// <summary>
/// Number of reliable channels
/// </summary>
internal const int NumReliableChannels = ((int)NetMessageType.UserReliableOrdered1 + NetConstants.NetChannelsPerDeliveryMethod) - (int)NetMessageType.UserReliableUnordered;
internal const string ConnResetMessage = "Connection was reset by remote host";
}
}

View File

@@ -0,0 +1,46 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Lidgren.Network
{
/// <summary>
/// How the library deals with resends and handling of late messages
/// </summary>
public enum NetDeliveryMethod : byte
{
//
// Actually a publicly visible subset of NetMessageType
//
/// <summary>
/// Indicates an error
/// </summary>
Unknown = 0,
/// <summary>
/// Unreliable, unordered delivery
/// </summary>
Unreliable = 1,
/// <summary>
/// Unreliable delivery, but automatically dropping late messages
/// </summary>
UnreliableSequenced = 2,
/// <summary>
/// Reliable delivery, but unordered
/// </summary>
ReliableUnordered = 34,
/// <summary>
/// Reliable delivery, except for late messages which are dropped
/// </summary>
ReliableSequenced = 35,
/// <summary>
/// Reliable, ordered delivery
/// </summary>
ReliableOrdered = 67,
}
}

View File

@@ -0,0 +1,144 @@
/* Copyright (c) 2010 Michael Lidgren
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.
*/
using System;
using System.Security.Cryptography;
using System.Text;
using System.Security;
namespace Lidgren.Network
{
/// <summary>
/// Methods to encrypt and decrypt data using the XTEA algorith
/// </summary>
public sealed class NetXtea
{
private const int m_blockSize = 8;
private const int m_keySize = 16;
private const int m_delta = unchecked((int)0x9E3779B9);
private readonly int m_numRounds;
private uint[] m_sum0;
private uint[] m_sum1;
/// <summary>
/// 16 byte key
/// </summary>
public NetXtea(byte[] key, int rounds)
{
if (key.Length < 16)
throw new NetException("Key too short!");
m_numRounds = rounds;
m_sum0 = new uint[m_numRounds];
m_sum1 = new uint[m_numRounds];
uint[] tmp = new uint[8];
int num2;
int index = num2 = 0;
while (index < 4)
{
tmp[index] = BitConverter.ToUInt32(key, num2);
index++;
num2 += 4;
}
for (index = num2 = 0; index < 32; index++)
{
m_sum0[index] = ((uint)num2) + tmp[num2 & 3];
num2 += -1640531527;
m_sum1[index] = ((uint)num2) + tmp[(num2 >> 11) & 3];
}
}
/// <summary>
/// 16 byte key
/// </summary>
public NetXtea(byte[] key)
: this(key, 32)
{
}
/// <summary>
/// String to hash for key
/// </summary>
public NetXtea(string key)
: this(SHA1.Create().ComputeHash(Encoding.ASCII.GetBytes(key)), 32)
{
}
public void EncryptBlock(
byte[] inBytes,
int inOff,
byte[] outBytes,
int outOff)
{
uint v0 = BytesToUInt(inBytes, inOff);
uint v1 = BytesToUInt(inBytes, inOff + 4);
for (int i = 0; i != m_numRounds; i++)
{
v0 += (((v1 << 4) ^ (v1 >> 5)) + v1) ^ m_sum0[i];
v1 += (((v0 << 4) ^ (v0 >> 5)) + v0) ^ m_sum1[i];
}
UIntToBytes(v0, outBytes, outOff);
UIntToBytes(v1, outBytes, outOff + 4);
return;
}
public void DecryptBlock(
byte[] inBytes,
int inOff,
byte[] outBytes,
int outOff)
{
// Pack bytes into integers
uint v0 = BytesToUInt(inBytes, inOff);
uint v1 = BytesToUInt(inBytes, inOff + 4);
for (int i = m_numRounds - 1; i >= 0; i--)
{
v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ m_sum1[i];
v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ m_sum0[i];
}
UIntToBytes(v0, outBytes, outOff);
UIntToBytes(v1, outBytes, outOff + 4);
return;
}
private static uint BytesToUInt(byte[] bytes, int offset)
{
uint retval = (uint)(bytes[offset] << 24);
retval |= (uint)(bytes[++offset] << 16);
retval |= (uint)(bytes[++offset] << 8);
return (retval | bytes[++offset]);
}
private static void UIntToBytes(uint value, byte[] destination, int destinationOffset)
{
destination[destinationOffset++] = (byte)(value >> 24);
destination[destinationOffset++] = (byte)(value >> 16);
destination[destinationOffset++] = (byte)(value >> 8);
destination[destinationOffset++] = (byte)value;
}
}
}

View File

@@ -0,0 +1,71 @@
/* Copyright (c) 2010 Michael Lidgren
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.
*/
using System;
using System.Diagnostics;
using System.Runtime.Serialization;
namespace Lidgren.Network
{
/// <summary>
/// Exception thrown in the Lidgren Network Library
/// </summary>
[Serializable]
public sealed class NetException : Exception
{
public NetException()
: base()
{
}
public NetException(string message)
: base(message)
{
}
public NetException(string message, Exception inner)
: base(message, inner)
{
}
private NetException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
}
/// <summary>
/// Throws an exception, in DEBUG only, if first parameter is false
/// </summary>
[Conditional("DEBUG")]
public static void Assert(bool isOk, string message)
{
if (!isOk)
throw new NetException(message);
}
/// <summary>
/// Throws an exception, in DEBUG only, if first parameter is false
/// </summary>
[Conditional("DEBUG")]
public static void Assert(bool isOk)
{
if (!isOk)
throw new NetException();
}
}
}

View File

@@ -0,0 +1,174 @@
using System;
namespace Lidgren.Network
{
internal static class NetFragmentationHelper
{
internal static int WriteHeader(
byte[] destination,
int ptr,
int group,
int totalBits,
int chunkByteSize,
int chunkNumber)
{
uint num1 = (uint)group;
while (num1 >= 0x80)
{
destination[ptr++] = (byte)(num1 | 0x80);
num1 = num1 >> 7;
}
destination[ptr++] = (byte)num1;
// write variable length fragment total bits
uint num2 = (uint)totalBits;
while (num2 >= 0x80)
{
destination[ptr++] = (byte)(num2 | 0x80);
num2 = num2 >> 7;
}
destination[ptr++] = (byte)num2;
// write variable length fragment chunk size
uint num3 = (uint)chunkByteSize;
while (num3 >= 0x80)
{
destination[ptr++] = (byte)(num3 | 0x80);
num3 = num3 >> 7;
}
destination[ptr++] = (byte)num3;
// write variable length fragment chunk number
uint num4 = (uint)chunkNumber;
while (num4 >= 0x80)
{
destination[ptr++] = (byte)(num4 | 0x80);
num4 = num4 >> 7;
}
destination[ptr++] = (byte)num4;
return ptr;
}
internal static int ReadHeader(byte[] buffer, int ptr, out int group, out int totalBits, out int chunkByteSize, out int chunkNumber)
{
int num1 = 0;
int num2 = 0;
while (true)
{
byte num3 = buffer[ptr++];
num1 |= (num3 & 0x7f) << (num2 & 0x1f);
num2 += 7;
if ((num3 & 0x80) == 0)
{
group = num1;
break;
}
}
num1 = 0;
num2 = 0;
while (true)
{
byte num3 = buffer[ptr++];
num1 |= (num3 & 0x7f) << (num2 & 0x1f);
num2 += 7;
if ((num3 & 0x80) == 0)
{
totalBits = num1;
break;
}
}
num1 = 0;
num2 = 0;
while (true)
{
byte num3 = buffer[ptr++];
num1 |= (num3 & 0x7f) << (num2 & 0x1f);
num2 += 7;
if ((num3 & 0x80) == 0)
{
chunkByteSize = num1;
break;
}
}
num1 = 0;
num2 = 0;
while (true)
{
byte num3 = buffer[ptr++];
num1 |= (num3 & 0x7f) << (num2 & 0x1f);
num2 += 7;
if ((num3 & 0x80) == 0)
{
chunkNumber = num1;
break;
}
}
return ptr;
}
internal static int GetFragmentationHeaderSize(int groupId, int totalBytes, int chunkByteSize, int numChunks)
{
int len = 4;
// write variable length fragment group id
uint num1 = (uint)groupId;
while (num1 >= 0x80)
{
len++;
num1 = num1 >> 7;
}
// write variable length fragment total bits
uint num2 = (uint)(totalBytes * 8);
while (num2 >= 0x80)
{
len++;
num2 = num2 >> 7;
}
// write variable length fragment chunk byte size
uint num3 = (uint)chunkByteSize;
while (num3 >= 0x80)
{
len++;
num3 = num3 >> 7;
}
// write variable length fragment chunk number
uint num4 = (uint)numChunks;
while (num4 >= 0x80)
{
len++;
num4 = num4 >> 7;
}
return len;
}
internal static int GetBestChunkSize(int group, int totalBytes, int mtu)
{
int tryNumChunks = (totalBytes / (mtu - 8)) + 1;
int tryChunkSize = (totalBytes / tryNumChunks) + 1; // +1 since we immediately decrement it in the loop
int headerSize = 0;
do
{
tryChunkSize--; // keep reducing chunk size until it fits within MTU including header
int numChunks = totalBytes / tryChunkSize;
if (numChunks * tryChunkSize < totalBytes)
numChunks++;
headerSize = GetFragmentationHeaderSize(group, totalBytes, tryChunkSize, numChunks);
} while (tryChunkSize + headerSize + 5 + 1 >= mtu);
return tryChunkSize;
}
}
}

View File

@@ -0,0 +1,12 @@
using System;
namespace Lidgren.Network
{
public sealed class NetFragmentationInfo
{
public int TotalFragmentCount;
public bool[] Received;
public int TotalReceived;
public int FragmentSize;
}
}

View File

@@ -0,0 +1,319 @@
/* Copyright (c) 2010 Michael Lidgren
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.
*/
using System;
using System.Diagnostics;
using System.Net;
namespace Lidgren.Network
{
public partial class NetIncomingMessage
{
//
// 1 bit
//
/// <summary>
/// Reads a 1-bit Boolean without advancing the read pointer
/// </summary>
public bool PeekBoolean()
{
NetException.Assert(m_bitLength - m_readPosition >= 1, c_readOverflowError);
byte retval = NetBitWriter.ReadByte(m_data, 1, m_readPosition);
return (retval > 0 ? true : false);
}
//
// 8 bit
//
/// <summary>
/// Reads a Byte without advancing the read pointer
/// </summary>
public byte PeekByte()
{
NetException.Assert(m_bitLength - m_readPosition >= 8, c_readOverflowError);
byte retval = NetBitWriter.ReadByte(m_data, 8, m_readPosition);
return retval;
}
/// <summary>
/// Reads an SByte without advancing the read pointer
/// </summary>
[CLSCompliant(false)]
public sbyte PeekSByte()
{
NetException.Assert(m_bitLength - m_readPosition >= 8, c_readOverflowError);
byte retval = NetBitWriter.ReadByte(m_data, 8, m_readPosition);
return (sbyte)retval;
}
/// <summary>
/// Reads the specified number of bits into a Byte without advancing the read pointer
/// </summary>
public byte PeekByte(int numberOfBits)
{
byte retval = NetBitWriter.ReadByte(m_data, numberOfBits, m_readPosition);
return retval;
}
/// <summary>
/// Reads the specified number of bytes without advancing the read pointer
/// </summary>
public byte[] PeekBytes(int numberOfBytes)
{
NetException.Assert(m_bitLength - m_readPosition >= (numberOfBytes * 8), c_readOverflowError);
byte[] retval = new byte[numberOfBytes];
NetBitWriter.ReadBytes(m_data, numberOfBytes, m_readPosition, retval, 0);
return retval;
}
/// <summary>
/// Reads the specified number of bytes without advancing the read pointer
/// </summary>
public void PeekBytes(byte[] into, int offset, int numberOfBytes)
{
NetException.Assert(m_bitLength - m_readPosition >= (numberOfBytes * 8), c_readOverflowError);
NetException.Assert(offset + numberOfBytes <= into.Length);
NetBitWriter.ReadBytes(m_data, numberOfBytes, m_readPosition, into, offset);
return;
}
//
// 16 bit
//
/// <summary>
/// Reads an Int16 without advancing the read pointer
/// </summary>
public Int16 PeekInt16()
{
NetException.Assert(m_bitLength - m_readPosition >= 16, c_readOverflowError);
uint retval = NetBitWriter.ReadUInt32(m_data, 16, m_readPosition);
return (short)retval;
}
/// <summary>
/// Reads a UInt16 without advancing the read pointer
/// </summary>
[CLSCompliant(false)]
public UInt16 PeekUInt16()
{
NetException.Assert(m_bitLength - m_readPosition >= 16, c_readOverflowError);
uint retval = NetBitWriter.ReadUInt32(m_data, 16, m_readPosition);
return (ushort)retval;
}
//
// 32 bit
//
/// <summary>
/// Reads an Int32 without advancing the read pointer
/// </summary>
public Int32 PeekInt32()
{
NetException.Assert(m_bitLength - m_readPosition >= 32, c_readOverflowError);
uint retval = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition);
return (Int32)retval;
}
/// <summary>
/// Reads the specified number of bits into an Int32 without advancing the read pointer
/// </summary>
public Int32 PeekInt32(int numberOfBits)
{
NetException.Assert((numberOfBits > 0 && numberOfBits <= 32), "ReadInt() can only read between 1 and 32 bits");
NetException.Assert(m_bitLength - m_readPosition >= numberOfBits, c_readOverflowError);
uint retval = NetBitWriter.ReadUInt32(m_data, numberOfBits, m_readPosition);
if (numberOfBits == 32)
return (int)retval;
int signBit = 1 << (numberOfBits - 1);
if ((retval & signBit) == 0)
return (int)retval; // positive
// negative
unchecked
{
uint mask = ((uint)-1) >> (33 - numberOfBits);
uint tmp = (retval & mask) + 1;
return -((int)tmp);
}
}
/// <summary>
/// Reads a UInt32 without advancing the read pointer
/// </summary>
[CLSCompliant(false)]
public UInt32 PeekUInt32()
{
NetException.Assert(m_bitLength - m_readPosition >= 32, c_readOverflowError);
uint retval = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition);
return retval;
}
/// <summary>
/// Reads the specified number of bits into a UInt32 without advancing the read pointer
/// </summary>
[CLSCompliant(false)]
public UInt32 PeekUInt32(int numberOfBits)
{
NetException.Assert((numberOfBits > 0 && numberOfBits <= 32), "ReadUInt() can only read between 1 and 32 bits");
//NetException.Assert(m_bitLength - m_readBitPtr >= numberOfBits, "tried to read past buffer size");
UInt32 retval = NetBitWriter.ReadUInt32(m_data, numberOfBits, m_readPosition);
return retval;
}
//
// 64 bit
//
/// <summary>
/// Reads a UInt64 without advancing the read pointer
/// </summary>
[CLSCompliant(false)]
public UInt64 PeekUInt64()
{
NetException.Assert(m_bitLength - m_readPosition >= 64, c_readOverflowError);
ulong low = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition);
ulong high = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition + 32);
ulong retval = low + (high << 32);
return retval;
}
/// <summary>
/// Reads an Int32 without advancing the read pointer
/// </summary>
public Int64 PeekInt64()
{
NetException.Assert(m_bitLength - m_readPosition >= 64, c_readOverflowError);
unchecked
{
ulong retval = PeekUInt64();
long longRetval = (long)retval;
return longRetval;
}
}
/// <summary>
/// Reads the specified number of bits into an UInt64 without advancing the read pointer
/// </summary>
[CLSCompliant(false)]
public UInt64 PeekUInt64(int numberOfBits)
{
NetException.Assert((numberOfBits > 0 && numberOfBits <= 64), "ReadUInt() can only read between 1 and 64 bits");
NetException.Assert(m_bitLength - m_readPosition >= numberOfBits, c_readOverflowError);
ulong retval;
if (numberOfBits <= 32)
{
retval = (ulong)NetBitWriter.ReadUInt32(m_data, numberOfBits, m_readPosition);
}
else
{
retval = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition);
retval |= NetBitWriter.ReadUInt32(m_data, numberOfBits - 32, m_readPosition) << 32;
}
return retval;
}
/// <summary>
/// Reads the specified number of bits into an Int64 without advancing the read pointer
/// </summary>
public Int64 PeekInt64(int numberOfBits)
{
NetException.Assert(((numberOfBits > 0) && (numberOfBits < 65)), "ReadInt64(bits) can only read between 1 and 64 bits");
return (long)PeekUInt64(numberOfBits);
}
//
// Floating point
//
/// <summary>
/// Reads a 32-bit Single without advancing the read pointer
/// </summary>
public float PeekFloat()
{
return PeekSingle();
}
/// <summary>
/// Reads a 32-bit Single without advancing the read pointer
/// </summary>
public float PeekSingle()
{
NetException.Assert(m_bitLength - m_readPosition >= 32, c_readOverflowError);
if ((m_readPosition & 7) == 0) // read directly
{
// endianness is handled inside BitConverter.ToSingle
float retval = BitConverter.ToSingle(m_data, m_readPosition >> 3);
return retval;
}
byte[] bytes = PeekBytes(4);
return BitConverter.ToSingle(bytes, 0); // endianness is handled inside BitConverter.ToSingle
}
/// <summary>
/// Reads a 64-bit Double without advancing the read pointer
/// </summary>
public double PeekDouble()
{
NetException.Assert(m_bitLength - m_readPosition >= 64, c_readOverflowError);
if ((m_readPosition & 7) == 0) // read directly
{
// read directly
double retval = BitConverter.ToDouble(m_data, m_readPosition >> 3);
return retval;
}
byte[] bytes = PeekBytes(8);
return BitConverter.ToDouble(bytes, 0); // endianness is handled inside BitConverter.ToSingle
}
/// <summary>
/// Reads a string without advancing the read pointer
/// </summary>
public string PeekString()
{
int byteLen = (int)ReadVariableUInt32();
if (byteLen == 0)
return String.Empty;
NetException.Assert(m_bitLength - m_readPosition >= (byteLen * 8), c_readOverflowError);
if ((m_readPosition & 7) == 0)
{
// read directly
string retval = System.Text.Encoding.UTF8.GetString(m_data, m_readPosition >> 3, byteLen);
return retval;
}
byte[] bytes = PeekBytes(byteLen);
return System.Text.Encoding.UTF8.GetString(bytes, 0, bytes.Length);
}
}
}

View File

@@ -0,0 +1,103 @@
/* Copyright (c) 2010 Michael Lidgren
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.
*/
using System;
using System.Reflection;
namespace Lidgren.Network
{
public partial class NetIncomingMessage
{
/// <summary>
/// Reads all public and private declared instance fields of the object in alphabetical order using reflection
/// </summary>
public void ReadAllFields(object target)
{
ReadAllFields(target, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
}
/// <summary>
/// Reads all fields with the specified binding of the object in alphabetical order using reflection
/// </summary>
public void ReadAllFields(object target, BindingFlags flags)
{
if (target == null)
return;
Type tp = target.GetType();
FieldInfo[] fields = tp.GetFields(flags);
NetUtility.SortMembersList(fields);
foreach (FieldInfo fi in fields)
{
object value;
// find read method
MethodInfo readMethod;
if (s_readMethods.TryGetValue(fi.FieldType, out readMethod))
{
// read value
value = readMethod.Invoke(this, null);
// set the value
fi.SetValue(target, value);
}
}
}
/// <summary>
/// Reads all public and private declared instance fields of the object in alphabetical order using reflection
/// </summary>
public void ReadAllProperties(object target)
{
ReadAllProperties(target, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
}
/// <summary>
/// Reads all fields with the specified binding of the object in alphabetical order using reflection
/// </summary>
public void ReadAllProperties(object target, BindingFlags flags)
{
if (target == null)
throw new ArgumentNullException("target");
if (target == null)
return;
Type tp = target.GetType();
PropertyInfo[] fields = tp.GetProperties(flags);
NetUtility.SortMembersList(fields);
foreach (PropertyInfo fi in fields)
{
object value;
// find read method
MethodInfo readMethod;
if (s_readMethods.TryGetValue(fi.PropertyType, out readMethod))
{
// read value
value = readMethod.Invoke(this, null);
// set the value
MethodInfo setMethod = fi.GetSetMethod((flags & BindingFlags.NonPublic) == BindingFlags.NonPublic);
setMethod.Invoke(target, new object[] { value });
}
}
}
}
}

View File

@@ -0,0 +1,484 @@
/* Copyright (c) 2010 Michael Lidgren
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.
*/
using System;
using System.Collections.Generic;
using System.Net;
using System.Reflection;
namespace Lidgren.Network
{
public partial class NetIncomingMessage
{
private const string c_readOverflowError = "Trying to read past the buffer size - likely caused by mismatching Write/Reads, different size or order.";
private static readonly Dictionary<Type, MethodInfo> s_readMethods;
internal int m_readPosition;
/// <summary>
/// Gets or sets the read position in the buffer, in bits (not bytes)
/// </summary>
public long Position
{
get { return (long)m_readPosition; }
set { m_readPosition = (int)value; }
}
/// <summary>
/// Gets the position in the buffer in bytes; note that the bits of the first returned byte may already have been read - check the Position property to make sure.
/// </summary>
public int PositionInBytes
{
get { return (int)(m_readPosition / 8); }
}
static NetIncomingMessage()
{
Type[] integralTypes = typeof(Byte).Assembly.GetTypes();
s_readMethods = new Dictionary<Type, MethodInfo>();
MethodInfo[] methods = typeof(NetIncomingMessage).GetMethods(BindingFlags.Instance | BindingFlags.Public);
foreach (MethodInfo mi in methods)
{
if (mi.GetParameters().Length == 0 && mi.Name.StartsWith("Read", StringComparison.InvariantCulture))
{
string n = mi.Name.Substring(4);
foreach (Type it in integralTypes)
{
if (it.Name == n)
s_readMethods[it] = mi;
}
}
}
}
//
// 1 bit
//
public bool ReadBoolean()
{
NetException.Assert(m_bitLength - m_readPosition >= 1, c_readOverflowError);
byte retval = NetBitWriter.ReadByte(m_data, 1, m_readPosition);
m_readPosition += 1;
return (retval > 0 ? true : false);
}
//
// 8 bit
//
public byte ReadByte()
{
NetException.Assert(m_bitLength - m_readPosition >= 8, c_readOverflowError);
byte retval = NetBitWriter.ReadByte(m_data, 8, m_readPosition);
m_readPosition += 8;
return retval;
}
[CLSCompliant(false)]
public sbyte ReadSByte()
{
NetException.Assert(m_bitLength - m_readPosition >= 8, c_readOverflowError);
byte retval = NetBitWriter.ReadByte(m_data, 8, m_readPosition);
m_readPosition += 8;
return (sbyte)retval;
}
public byte ReadByte(int numberOfBits)
{
byte retval = NetBitWriter.ReadByte(m_data, numberOfBits, m_readPosition);
m_readPosition += numberOfBits;
return retval;
}
public byte[] ReadBytes(int numberOfBytes)
{
NetException.Assert(m_bitLength - m_readPosition >= (numberOfBytes * 8), c_readOverflowError);
byte[] retval = new byte[numberOfBytes];
NetBitWriter.ReadBytes(m_data, numberOfBytes, m_readPosition, retval, 0);
m_readPosition += (8 * numberOfBytes);
return retval;
}
public void ReadBytes(byte[] into, int offset, int numberOfBytes)
{
NetException.Assert(m_bitLength - m_readPosition >= (numberOfBytes * 8), c_readOverflowError);
NetException.Assert(offset + numberOfBytes <= into.Length);
NetBitWriter.ReadBytes(m_data, numberOfBytes, m_readPosition, into, offset);
m_readPosition += (8 * numberOfBytes);
return;
}
public void ReadBits(byte[] into, int offset, int numberOfBits)
{
NetException.Assert(m_bitLength - m_readPosition >= numberOfBits, c_readOverflowError);
NetException.Assert(offset + NetUtility.BytesToHoldBits(numberOfBits) <= into.Length);
int numberOfWholeBytes = numberOfBits / 8;
int extraBits = numberOfBits - (numberOfWholeBytes * 8);
NetBitWriter.ReadBytes(m_data, numberOfWholeBytes, m_readPosition, into, offset);
m_readPosition += (8 * numberOfWholeBytes);
if (extraBits > 0)
into[offset + numberOfWholeBytes] = ReadByte(extraBits);
return;
}
//
// 16 bit
//
public Int16 ReadInt16()
{
NetException.Assert(m_bitLength - m_readPosition >= 16, c_readOverflowError);
uint retval = NetBitWriter.ReadUInt32(m_data, 16, m_readPosition);
m_readPosition += 16;
return (short)retval;
}
[CLSCompliant(false)]
public UInt16 ReadUInt16()
{
NetException.Assert(m_bitLength - m_readPosition >= 16, c_readOverflowError);
uint retval = NetBitWriter.ReadUInt32(m_data, 16, m_readPosition);
m_readPosition += 16;
return (ushort)retval;
}
//
// 32 bit
//
public Int32 ReadInt32()
{
NetException.Assert(m_bitLength - m_readPosition >= 32, c_readOverflowError);
uint retval = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition);
m_readPosition += 32;
return (Int32)retval;
}
public Int32 ReadInt32(int numberOfBits)
{
NetException.Assert((numberOfBits > 0 && numberOfBits <= 32), "ReadInt() can only read between 1 and 32 bits");
NetException.Assert(m_bitLength - m_readPosition >= numberOfBits, c_readOverflowError);
uint retval = NetBitWriter.ReadUInt32(m_data, numberOfBits, m_readPosition);
m_readPosition += numberOfBits;
if (numberOfBits == 32)
return (int)retval;
int signBit = 1 << (numberOfBits - 1);
if ((retval & signBit) == 0)
return (int)retval; // positive
// negative
unchecked
{
uint mask = ((uint)-1) >> (33 - numberOfBits);
uint tmp = (retval & mask) + 1;
return -((int)tmp);
}
}
[CLSCompliant(false)]
public UInt32 ReadUInt32()
{
NetException.Assert(m_bitLength - m_readPosition >= 32, c_readOverflowError);
uint retval = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition);
m_readPosition += 32;
return retval;
}
[CLSCompliant(false)]
public UInt32 ReadUInt32(int numberOfBits)
{
NetException.Assert((numberOfBits > 0 && numberOfBits <= 32), "ReadUInt() can only read between 1 and 32 bits");
//NetException.Assert(m_bitLength - m_readBitPtr >= numberOfBits, "tried to read past buffer size");
UInt32 retval = NetBitWriter.ReadUInt32(m_data, numberOfBits, m_readPosition);
m_readPosition += numberOfBits;
return retval;
}
//
// 64 bit
//
[CLSCompliant(false)]
public UInt64 ReadUInt64()
{
NetException.Assert(m_bitLength - m_readPosition >= 64, c_readOverflowError);
ulong low = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition);
m_readPosition += 32;
ulong high = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition);
ulong retval = low + (high << 32);
m_readPosition += 32;
return retval;
}
public Int64 ReadInt64()
{
NetException.Assert(m_bitLength - m_readPosition >= 64, c_readOverflowError);
unchecked
{
ulong retval = ReadUInt64();
long longRetval = (long)retval;
return longRetval;
}
}
[CLSCompliant(false)]
public UInt64 ReadUInt64(int numberOfBits)
{
NetException.Assert((numberOfBits > 0 && numberOfBits <= 64), "ReadUInt() can only read between 1 and 64 bits");
NetException.Assert(m_bitLength - m_readPosition >= numberOfBits, c_readOverflowError);
ulong retval;
if (numberOfBits <= 32)
{
retval = (ulong)NetBitWriter.ReadUInt32(m_data, numberOfBits, m_readPosition);
}
else
{
retval = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition);
retval |= NetBitWriter.ReadUInt32(m_data, numberOfBits - 32, m_readPosition) << 32;
}
m_readPosition += numberOfBits;
return retval;
}
public Int64 ReadInt64(int numberOfBits)
{
NetException.Assert(((numberOfBits > 0) && (numberOfBits < 65)), "ReadInt64(bits) can only read between 1 and 64 bits");
return (long)ReadUInt64(numberOfBits);
}
//
// Floating point
//
public float ReadFloat()
{
return ReadSingle();
}
public float ReadSingle()
{
NetException.Assert(m_bitLength - m_readPosition >= 32, c_readOverflowError);
if ((m_readPosition & 7) == 0) // read directly
{
// endianness is handled inside BitConverter.ToSingle
float retval = BitConverter.ToSingle(m_data, m_readPosition >> 3);
m_readPosition += 32;
return retval;
}
byte[] bytes = ReadBytes(4);
return BitConverter.ToSingle(bytes, 0); // endianness is handled inside BitConverter.ToSingle
}
public double ReadDouble()
{
NetException.Assert(m_bitLength - m_readPosition >= 64, c_readOverflowError);
if ((m_readPosition & 7) == 0) // read directly
{
// read directly
double retval = BitConverter.ToDouble(m_data, m_readPosition >> 3);
m_readPosition += 64;
return retval;
}
byte[] bytes = ReadBytes(8);
return BitConverter.ToDouble(bytes, 0); // endianness is handled inside BitConverter.ToSingle
}
//
// Variable bit count
//
/// <summary>
/// Reads a UInt32 written using WriteVariableUInt32()
/// </summary>
[CLSCompliant(false)]
public uint ReadVariableUInt32()
{
int num1 = 0;
int num2 = 0;
while (true)
{
if (num2 == 0x23)
throw new FormatException("Bad 7-bit encoded integer");
byte num3 = this.ReadByte();
num1 |= (num3 & 0x7f) << num2;
num2 += 7;
if ((num3 & 0x80) == 0)
return (uint)num1;
}
}
/// <summary>
/// Reads a Int32 written using WriteVariableInt32()
/// </summary>
public int ReadVariableInt32()
{
int num1 = 0;
int num2 = 0;
while (true)
{
if (num2 == 0x23)
throw new FormatException("Bad 7-bit encoded integer");
byte num3 = this.ReadByte();
num1 |= (num3 & 0x7f) << (num2 & 0x1f);
num2 += 7;
if ((num3 & 0x80) == 0)
{
int sign = (num1 << 31) >> 31;
return sign ^ (num1 >> 1);
}
}
}
/// <summary>
/// Reads a UInt32 written using WriteVariableInt64()
/// </summary>
[CLSCompliant(false)]
public UInt64 ReadVariableUInt64()
{
UInt64 num1 = 0;
int num2 = 0;
while (true)
{
if (num2 == 0x23)
throw new FormatException("Bad 7-bit encoded integer");
byte num3 = this.ReadByte();
num1 |= ((UInt64)num3 & 0x7f) << num2;
num2 += 7;
if ((num3 & 0x80) == 0)
return num1;
}
}
/// <summary>
/// Reads a float written using WriteSignedSingle()
/// </summary>
public float ReadSignedSingle(int numberOfBits)
{
uint encodedVal = ReadUInt32(numberOfBits);
int maxVal = (1 << numberOfBits) - 1;
return ((float)(encodedVal + 1) / (float)(maxVal + 1) - 0.5f) * 2.0f;
}
/// <summary>
/// Reads a float written using WriteUnitSingle()
/// </summary>
public float ReadUnitSingle(int numberOfBits)
{
uint encodedVal = ReadUInt32(numberOfBits);
int maxVal = (1 << numberOfBits) - 1;
return (float)(encodedVal + 1) / (float)(maxVal + 1);
}
/// <summary>
/// Reads a float written using WriteRangedSingle() using the same MIN and MAX values
/// </summary>
public float ReadRangedSingle(float min, float max, int numberOfBits)
{
float range = max - min;
int maxVal = (1 << numberOfBits) - 1;
float encodedVal = (float)ReadUInt32(numberOfBits);
float unit = encodedVal / (float)maxVal;
return min + (unit * range);
}
/// <summary>
/// Reads an integer written using WriteRangedInteger() using the same min/max values
/// </summary>
public int ReadRangedInteger(int min, int max)
{
uint range = (uint)(max - min);
int numBits = NetUtility.BitsToHoldUInt(range);
uint rvalue = ReadUInt32(numBits);
return (int)(min + rvalue);
}
/// <summary>
/// Reads a string
/// </summary>
public string ReadString()
{
int byteLen = (int)ReadVariableUInt32();
if (byteLen == 0)
return String.Empty;
NetException.Assert(m_bitLength - m_readPosition >= (byteLen * 8), c_readOverflowError);
if ((m_readPosition & 7) == 0)
{
// read directly
string retval = System.Text.Encoding.UTF8.GetString(m_data, m_readPosition >> 3, byteLen);
m_readPosition += (8 * byteLen);
return retval;
}
byte[] bytes = ReadBytes(byteLen);
return System.Text.Encoding.UTF8.GetString(bytes, 0, bytes.Length);
}
/// <summary>
/// Reads a stored IPv4 endpoint description
/// </summary>
public IPEndPoint ReadIPEndpoint()
{
byte len = ReadByte();
byte[] addressBytes = ReadBytes(len);
int port = (int)ReadUInt16();
IPAddress address = new IPAddress(addressBytes);
return new IPEndPoint(address, port);
}
/// <summary>
/// Pads data with enough bits to reach a full byte. Decreases cpu usage for subsequent byte writes.
/// </summary>
public void SkipPadBits()
{
m_readPosition = ((m_readPosition + 7) >> 3) * 8;
}
/// <summary>
/// Pads data with the specified number of bits.
/// </summary>
public void SkipPadBits(int numberOfBits)
{
m_readPosition += numberOfBits;
}
}
}

View File

@@ -0,0 +1,485 @@
/* Copyright (c) 2010 Michael Lidgren
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.
*/
using System;
using System.Collections.Generic;
using System.Net;
using System.Diagnostics;
using System.Text;
namespace Lidgren.Network
{
public partial class NetIncomingMessage
{
/// <summary>
/// Ensures the buffer can hold this number of bits
/// </summary>
private void InternalEnsureBufferSize(int numberOfBits)
{
int byteLen = ((numberOfBits + 7) >> 3);
if (m_data == null)
{
m_data = new byte[byteLen];
return;
}
if (m_data.Length < byteLen)
Array.Resize<byte>(ref m_data, byteLen);
return;
}
//
// 1 bit
//
internal void Write(bool value)
{
InternalEnsureBufferSize(m_bitLength + 1);
NetBitWriter.WriteByte((value ? (byte)1 : (byte)0), 1, m_data, m_bitLength);
m_bitLength += 1;
}
//
// 8 bit
//
internal void Write(byte source)
{
InternalEnsureBufferSize(m_bitLength + 8);
NetBitWriter.WriteByte(source, 8, m_data, m_bitLength);
m_bitLength += 8;
}
internal void Write(sbyte source)
{
InternalEnsureBufferSize(m_bitLength + 8);
NetBitWriter.WriteByte((byte)source, 8, m_data, m_bitLength);
m_bitLength += 8;
}
internal void Write(byte source, int numberOfBits)
{
NetException.Assert((numberOfBits > 0 && numberOfBits <= 8), "Write(byte, numberOfBits) can only write between 1 and 8 bits");
InternalEnsureBufferSize(m_bitLength + numberOfBits);
NetBitWriter.WriteByte(source, numberOfBits, m_data, m_bitLength);
m_bitLength += numberOfBits;
}
internal void Write(byte[] source)
{
if (source == null)
throw new ArgumentNullException("source");
int bits = source.Length * 8;
InternalEnsureBufferSize(m_bitLength + bits);
NetBitWriter.WriteBytes(source, 0, source.Length, m_data, m_bitLength);
m_bitLength += bits;
}
internal void Write(byte[] source, int offsetInBytes, int numberOfBytes)
{
if (source == null)
throw new ArgumentNullException("source");
int bits = numberOfBytes * 8;
InternalEnsureBufferSize(m_bitLength + bits);
NetBitWriter.WriteBytes(source, offsetInBytes, numberOfBytes, m_data, m_bitLength);
m_bitLength += bits;
}
//
// 16 bit
//
internal void Write(UInt16 source)
{
InternalEnsureBufferSize(m_bitLength + 16);
NetBitWriter.WriteUInt32((uint)source, 16, m_data, m_bitLength);
m_bitLength += 16;
}
internal void Write(UInt16 source, int numberOfBits)
{
NetException.Assert((numberOfBits > 0 && numberOfBits <= 16), "Write(ushort, numberOfBits) can only write between 1 and 16 bits");
InternalEnsureBufferSize(m_bitLength + numberOfBits);
NetBitWriter.WriteUInt32((uint)source, numberOfBits, m_data, m_bitLength);
m_bitLength += numberOfBits;
}
internal void Write(Int16 source)
{
InternalEnsureBufferSize(m_bitLength + 16);
NetBitWriter.WriteUInt32((uint)source, 16, m_data, m_bitLength);
m_bitLength += 16;
}
//
// 32 bit
//
#if UNSAFE
internal unsafe void Write(Int32 source)
{
EnsureBufferSize(m_bitLength + 32);
// can write fast?
if (m_bitLength % 8 == 0)
{
fixed (byte* numRef = &Data[m_bitLength / 8])
{
*((int*)numRef) = source;
}
}
else
{
NetBitWriter.WriteUInt32((UInt32)source, 32, Data, m_bitLength);
}
m_bitLength += 32;
}
#else
internal void Write(Int32 source)
{
InternalEnsureBufferSize(m_bitLength + 32);
NetBitWriter.WriteUInt32((UInt32)source, 32, m_data, m_bitLength);
m_bitLength += 32;
}
#endif
#if UNSAFE
internal unsafe void Write(UInt32 source)
{
EnsureBufferSize(m_bitLength + 32);
// can write fast?
if (m_bitLength % 8 == 0)
{
fixed (byte* numRef = &Data[m_bitLength / 8])
{
*((uint*)numRef) = source;
}
}
else
{
NetBitWriter.WriteUInt32(source, 32, Data, m_bitLength);
}
m_bitLength += 32;
}
#else
internal void Write(UInt32 source)
{
InternalEnsureBufferSize(m_bitLength + 32);
NetBitWriter.WriteUInt32(source, 32, m_data, m_bitLength);
m_bitLength += 32;
}
#endif
internal void Write(UInt32 source, int numberOfBits)
{
NetException.Assert((numberOfBits > 0 && numberOfBits <= 32), "Write(uint, numberOfBits) can only write between 1 and 32 bits");
InternalEnsureBufferSize(m_bitLength + numberOfBits);
NetBitWriter.WriteUInt32(source, numberOfBits, m_data, m_bitLength);
m_bitLength += numberOfBits;
}
internal void Write(Int32 source, int numberOfBits)
{
NetException.Assert((numberOfBits > 0 && numberOfBits <= 32), "Write(int, numberOfBits) can only write between 1 and 32 bits");
InternalEnsureBufferSize(m_bitLength + numberOfBits);
if (numberOfBits != 32)
{
// make first bit sign
int signBit = 1 << (numberOfBits - 1);
if (source < 0)
source = (-source - 1) | signBit;
else
source &= (~signBit);
}
NetBitWriter.WriteUInt32((uint)source, numberOfBits, m_data, m_bitLength);
m_bitLength += numberOfBits;
}
//
// 64 bit
//
internal void Write(UInt64 source)
{
InternalEnsureBufferSize(m_bitLength + 64);
NetBitWriter.WriteUInt64(source, 64, m_data, m_bitLength);
m_bitLength += 64;
}
internal void Write(UInt64 source, int numberOfBits)
{
InternalEnsureBufferSize(m_bitLength + numberOfBits);
NetBitWriter.WriteUInt64(source, numberOfBits, m_data, m_bitLength);
m_bitLength += numberOfBits;
}
internal void Write(Int64 source)
{
InternalEnsureBufferSize(m_bitLength + 64);
ulong usource = (ulong)source;
NetBitWriter.WriteUInt64(usource, 64, m_data, m_bitLength);
m_bitLength += 64;
}
internal void Write(Int64 source, int numberOfBits)
{
InternalEnsureBufferSize(m_bitLength + numberOfBits);
ulong usource = (ulong)source;
NetBitWriter.WriteUInt64(usource, numberOfBits, m_data, m_bitLength);
m_bitLength += numberOfBits;
}
//
// Floating point
//
#if UNSAFE
internal unsafe void Write(float source)
{
uint val = *((uint*)&source);
#if BIGENDIAN
val = NetUtility.SwapByteOrder(val);
#endif
Write(val);
}
#else
internal void Write(float source)
{
byte[] val = BitConverter.GetBytes(source);
#if BIGENDIAN
// swap byte order
byte tmp = val[3];
val[3] = val[0];
val[0] = tmp;
tmp = val[2];
val[2] = val[1];
val[1] = tmp;
#endif
Write(val);
}
#endif
#if UNSAFE
internal unsafe void Write(double source)
{
ulong val = *((ulong*)&source);
#if BIGENDIAN
val = NetUtility.SwapByteOrder(val);
#endif
Write(val);
}
#else
internal void Write(double source)
{
byte[] val = BitConverter.GetBytes(source);
#if BIGENDIAN
// 0 1 2 3 4 5 6 7
// swap byte order
byte tmp = val[7];
val[7] = val[0];
val[0] = tmp;
tmp = val[6];
val[6] = val[1];
val[1] = tmp;
tmp = val[5];
val[5] = val[2];
val[2] = tmp;
tmp = val[4];
val[4] = val[3];
val[3] = tmp;
#endif
Write(val);
}
#endif
//
// Variable bits
//
/// <summary>
/// Write Base128 encoded variable sized unsigned integer
/// </summary>
/// <returns>number of bytes written</returns>
internal int WriteVariableUInt32(uint value)
{
int retval = 1;
uint num1 = (uint)value;
while (num1 >= 0x80)
{
this.Write((byte)(num1 | 0x80));
num1 = num1 >> 7;
retval++;
}
this.Write((byte)num1);
return retval;
}
/// <summary>
/// Write Base128 encoded variable sized signed integer
/// </summary>
/// <returns>number of bytes written</returns>
internal int WriteVariableInt32(int value)
{
int retval = 1;
uint num1 = (uint)((value << 1) ^ (value >> 31));
while (num1 >= 0x80)
{
this.Write((byte)(num1 | 0x80));
num1 = num1 >> 7;
retval++;
}
this.Write((byte)num1);
return retval;
}
/// <summary>
/// Write Base128 encoded variable sized unsigned integer
/// </summary>
/// <returns>number of bytes written</returns>
internal int WriteVariableUInt64(UInt64 value)
{
int retval = 1;
UInt64 num1 = (UInt64)value;
while (num1 >= 0x80)
{
this.Write((byte)(num1 | 0x80));
num1 = num1 >> 7;
retval++;
}
this.Write((byte)num1);
return retval;
}
/// <summary>
/// Compress (lossy) a float in the range -1..1 using numberOfBits bits
/// </summary>
internal void WriteSignedSingle(float value, int numberOfBits)
{
NetException.Assert(((value >= -1.0) && (value <= 1.0)), " WriteSignedSingle() must be passed a float in the range -1 to 1; val is " + value);
float unit = (value + 1.0f) * 0.5f;
int maxVal = (1 << numberOfBits) - 1;
uint writeVal = (uint)(unit * (float)maxVal);
Write(writeVal, numberOfBits);
}
/// <summary>
/// Compress (lossy) a float in the range 0..1 using numberOfBits bits
/// </summary>
internal void WriteUnitSingle(float value, int numberOfBits)
{
NetException.Assert(((value >= 0.0) && (value <= 1.0)), " WriteUnitSingle() must be passed a float in the range 0 to 1; val is " + value);
int maxValue = (1 << numberOfBits) - 1;
uint writeVal = (uint)(value * (float)maxValue);
Write(writeVal, numberOfBits);
}
/// <summary>
/// Compress a float within a specified range using a certain number of bits
/// </summary>
internal void WriteRangedSingle(float value, float min, float max, int numberOfBits)
{
NetException.Assert(((value >= min) && (value <= max)), " WriteRangedSingle() must be passed a float in the range MIN to MAX; val is " + value);
float range = max - min;
float unit = ((value - min) / range);
int maxVal = (1 << numberOfBits) - 1;
Write((UInt32)((float)maxVal * unit), numberOfBits);
}
/// <summary>
/// Writes an integer with the least amount of bits need for the specified range
/// Returns number of bits written
/// </summary>
internal int WriteRangedInteger(int min, int max, int value)
{
NetException.Assert(value >= min && value <= max, "Value not within min/max range!");
uint range = (uint)(max - min);
int numBits = NetUtility.BitsToHoldUInt(range);
uint rvalue = (uint)(value - min);
Write(rvalue, numBits);
return numBits;
}
/// <summary>
/// Write a string
/// </summary>
internal void Write(string source)
{
if (string.IsNullOrEmpty(source))
{
InternalEnsureBufferSize(m_bitLength + 8);
WriteVariableUInt32(0);
return;
}
byte[] bytes = Encoding.UTF8.GetBytes(source);
// determine number of bytes to store length
int lenBytesNeeded = 1;
uint num1 = (uint)bytes.Length;
while (num1 >= 0x80)
{
num1 = num1 >> 7;
lenBytesNeeded++;
}
InternalEnsureBufferSize(m_bitLength + ((bytes.Length + lenBytesNeeded) * 8));
WriteVariableUInt32((uint)bytes.Length);
Write(bytes);
}
/// <summary>
/// Writes an endpoint description
/// </summary>
internal void Write(IPEndPoint endPoint)
{
byte[] bytes = endPoint.Address.GetAddressBytes();
Write((byte)bytes.Length);
Write(bytes);
Write((ushort)endPoint.Port);
}
/// <summary>
/// Pads data with enough bits to reach a full byte. Decreases cpu usage for subsequent byte writes.
/// </summary>
internal void WritePadBits()
{
m_bitLength = ((m_bitLength + 7) / 8) * 8;
InternalEnsureBufferSize(m_bitLength);
}
/// <summary>
/// Pads data with the specified number of bits.
/// </summary>
internal void WritePadBits(int numberOfBits)
{
m_bitLength += numberOfBits;
InternalEnsureBufferSize(m_bitLength);
}
}
}

View File

@@ -0,0 +1,122 @@
/* Copyright (c) 2010 Michael Lidgren
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.
*/
using System;
using System.Net;
using System.Diagnostics;
namespace Lidgren.Network
{
/// <summary>
/// Incoming message either sent from a remote peer or generated within the library
/// </summary>
[DebuggerDisplay("Type={MessageType} LengthBits={LengthBits}")]
public partial class NetIncomingMessage
{
internal byte[] m_data;
internal int m_bitLength;
internal NetIncomingMessageType m_incomingMessageType;
internal IPEndPoint m_senderEndpoint;
internal NetConnection m_senderConnection;
internal int m_sequenceNumber;
internal NetMessageType m_receivedMessageType;
internal bool m_isFragment;
/// <summary>
/// Gets the type of this incoming message
/// </summary>
public NetIncomingMessageType MessageType { get { return m_incomingMessageType; } }
/// <summary>
/// Gets the delivery method this message was sent with (if user data)
/// </summary>
public NetDeliveryMethod DeliveryMethod { get { return NetUtility.GetDeliveryMethod(m_receivedMessageType); } }
/// <summary>
/// Gets the sequence channel this message was sent with (if user data)
/// </summary>
public int SequenceChannel { get { return (int)m_receivedMessageType - (int)NetUtility.GetDeliveryMethod(m_receivedMessageType); } }
/// <summary>
/// IPEndPoint of sender, if any
/// </summary>
public IPEndPoint SenderEndpoint { get { return m_senderEndpoint; } }
/// <summary>
/// NetConnection of sender, if any
/// </summary>
public NetConnection SenderConnection { get { return m_senderConnection; } }
/// <summary>
/// Gets the length of the message payload in bytes
/// </summary>
public int LengthBytes
{
get { return ((m_bitLength + 7) >> 3); }
}
/// <summary>
/// Gets the length of the message payload in bits
/// </summary>
public int LengthBits
{
get { return m_bitLength; }
internal set { m_bitLength = value; }
}
internal NetIncomingMessage()
{
}
internal NetIncomingMessage(NetIncomingMessageType tp)
{
m_incomingMessageType = tp;
}
internal void Reset()
{
m_incomingMessageType = NetIncomingMessageType.Error;
m_readPosition = 0;
m_receivedMessageType = NetMessageType.LibraryError;
m_senderConnection = null;
m_bitLength = 0;
m_isFragment = false;
}
public void Decrypt(NetXtea tea)
{
// requires blocks of 8 bytes
int blocks = m_bitLength / 64;
if (blocks * 64 != m_bitLength)
throw new NetException("Wrong message length for XTEA decrypt! Length is " + m_bitLength + " bits");
byte[] result = new byte[m_data.Length];
for (int i = 0; i < blocks; i++)
tea.DecryptBlock(m_data, (i * 8), result, (i * 8));
m_data = result;
}
/// <summary>
/// Returns a string that represents this object
/// </summary>
public override string ToString()
{
return "[NetIncomingMessage #" + m_sequenceNumber + " " + this.LengthBytes + " bytes]";
}
}
}

View File

@@ -0,0 +1,46 @@
/* Copyright (c) 2010 Michael Lidgren
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.
*/
using System;
using System.Diagnostics.CodeAnalysis;
namespace Lidgren.Network
{
/// <summary>
/// The type of a NetIncomingMessage
/// </summary>
[SuppressMessage("Microsoft.Design", "CA1027:MarkEnumsWithFlags")]
public enum NetIncomingMessageType
{
// library note: values are power-of-two, but they are not flags - it's a convenience for NetPeerConfiguration.DisabledMessageTypes
Error = 0,
StatusChanged = 1 << 0, // Data (string)
UnconnectedData = 1 << 1, // Data Based on data received
ConnectionApproval = 1 << 2, // Data
Data = 1 << 3, // Data Based on data received
Receipt = 1 << 4, // Data
DiscoveryRequest = 1 << 5, // (no data)
DiscoveryResponse = 1 << 6, // Data
VerboseDebugMessage = 1 << 7, // Data (string)
DebugMessage = 1 << 8, // Data (string)
WarningMessage = 1 << 9, // Data (string)
ErrorMessage = 1 << 10, // Data (string)
NatIntroductionSuccess = 1 << 11, // Data (as passed to master server)
}
}

View File

@@ -0,0 +1,173 @@
/* Copyright (c) 2010 Michael Lidgren
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.
*/
using System;
namespace Lidgren.Network
{
internal enum NetMessageType : byte
{
Unconnected = 0,
UserUnreliable = 1,
UserSequenced1 = 2,
UserSequenced2 = 3,
UserSequenced3 = 4,
UserSequenced4 = 5,
UserSequenced5 = 6,
UserSequenced6 = 7,
UserSequenced7 = 8,
UserSequenced8 = 9,
UserSequenced9 = 10,
UserSequenced10 = 11,
UserSequenced11 = 12,
UserSequenced12 = 13,
UserSequenced13 = 14,
UserSequenced14 = 15,
UserSequenced15 = 16,
UserSequenced16 = 17,
UserSequenced17 = 18,
UserSequenced18 = 19,
UserSequenced19 = 20,
UserSequenced20 = 21,
UserSequenced21 = 22,
UserSequenced22 = 23,
UserSequenced23 = 24,
UserSequenced24 = 25,
UserSequenced25 = 26,
UserSequenced26 = 27,
UserSequenced27 = 28,
UserSequenced28 = 29,
UserSequenced29 = 30,
UserSequenced30 = 31,
UserSequenced31 = 32,
UserSequenced32 = 33,
UserReliableUnordered = 34,
UserReliableSequenced1 = 35,
UserReliableSequenced2 = 36,
UserReliableSequenced3 = 37,
UserReliableSequenced4 = 38,
UserReliableSequenced5 = 39,
UserReliableSequenced6 = 40,
UserReliableSequenced7 = 41,
UserReliableSequenced8 = 42,
UserReliableSequenced9 = 43,
UserReliableSequenced10 = 44,
UserReliableSequenced11 = 45,
UserReliableSequenced12 = 46,
UserReliableSequenced13 = 47,
UserReliableSequenced14 = 48,
UserReliableSequenced15 = 49,
UserReliableSequenced16 = 50,
UserReliableSequenced17 = 51,
UserReliableSequenced18 = 52,
UserReliableSequenced19 = 53,
UserReliableSequenced20 = 54,
UserReliableSequenced21 = 55,
UserReliableSequenced22 = 56,
UserReliableSequenced23 = 57,
UserReliableSequenced24 = 58,
UserReliableSequenced25 = 59,
UserReliableSequenced26 = 60,
UserReliableSequenced27 = 61,
UserReliableSequenced28 = 62,
UserReliableSequenced29 = 63,
UserReliableSequenced30 = 64,
UserReliableSequenced31 = 65,
UserReliableSequenced32 = 66,
UserReliableOrdered1 = 67,
UserReliableOrdered2 = 68,
UserReliableOrdered3 = 69,
UserReliableOrdered4 = 70,
UserReliableOrdered5 = 71,
UserReliableOrdered6 = 72,
UserReliableOrdered7 = 73,
UserReliableOrdered8 = 74,
UserReliableOrdered9 = 75,
UserReliableOrdered10 = 76,
UserReliableOrdered11 = 77,
UserReliableOrdered12 = 78,
UserReliableOrdered13 = 79,
UserReliableOrdered14 = 80,
UserReliableOrdered15 = 81,
UserReliableOrdered16 = 82,
UserReliableOrdered17 = 83,
UserReliableOrdered18 = 84,
UserReliableOrdered19 = 85,
UserReliableOrdered20 = 86,
UserReliableOrdered21 = 87,
UserReliableOrdered22 = 88,
UserReliableOrdered23 = 89,
UserReliableOrdered24 = 90,
UserReliableOrdered25 = 91,
UserReliableOrdered26 = 92,
UserReliableOrdered27 = 93,
UserReliableOrdered28 = 94,
UserReliableOrdered29 = 95,
UserReliableOrdered30 = 96,
UserReliableOrdered31 = 97,
UserReliableOrdered32 = 98,
Unused1 = 99,
Unused2 = 100,
Unused3 = 101,
Unused4 = 102,
Unused5 = 103,
Unused6 = 104,
Unused7 = 105,
Unused8 = 106,
Unused9 = 107,
Unused10 = 108,
Unused11 = 109,
Unused12 = 110,
Unused13 = 111,
Unused14 = 112,
Unused15 = 113,
Unused16 = 114,
Unused17 = 115,
Unused18 = 116,
Unused19 = 117,
Unused20 = 118,
Unused21 = 119,
Unused22 = 120,
Unused23 = 121,
Unused24 = 122,
Unused25 = 123,
Unused26 = 124,
Unused27 = 125,
Unused28 = 126,
Unused29 = 127,
LibraryError = 128,
Ping = 129, // used for RTT calculation
Pong = 130, // used for RTT calculation
Connect = 131,
ConnectResponse = 132,
ConnectionEstablished = 133,
Acknowledge = 134,
Disconnect = 135,
Discovery = 136,
DiscoveryResponse = 137,
NatPunchMessage = 138, // send between peers
NatIntroduction = 139, // send to master server
}
}

View File

@@ -0,0 +1,105 @@
using System;
using System.Collections.Generic;
using System.Net;
namespace Lidgren.Network
{
public partial class NetPeer
{
/// <summary>
/// Send NetIntroduction to hostExternal and clientExternal; introducing client to host
/// </summary>
public void Introduce(
IPEndPoint hostInternal,
IPEndPoint hostExternal,
IPEndPoint clientInternal,
IPEndPoint clientExternal,
string token)
{
// send message to client
NetOutgoingMessage msg = CreateMessage(10 + token.Length + 1);
msg.m_messageType = NetMessageType.NatIntroduction;
msg.Write(false);
msg.WritePadBits();
msg.Write(hostInternal);
msg.Write(hostExternal);
msg.Write(token);
m_unsentUnconnectedMessages.Enqueue(new NetTuple<IPEndPoint, NetOutgoingMessage>(clientExternal, msg));
// send message to host
msg = CreateMessage(10 + token.Length + 1);
msg.m_messageType = NetMessageType.NatIntroduction;
msg.Write(true);
msg.WritePadBits();
msg.Write(clientInternal);
msg.Write(clientExternal);
msg.Write(token);
m_unsentUnconnectedMessages.Enqueue(new NetTuple<IPEndPoint, NetOutgoingMessage>(hostExternal, msg));
}
/// <summary>
/// Called when host/client receives a NatIntroduction message from a master server
/// </summary>
private void HandleNatIntroduction(int ptr)
{
VerifyNetworkThread();
// read intro
NetIncomingMessage tmp = SetupReadHelperMessage(ptr, 1000); // never mind length
byte hostByte = tmp.ReadByte();
IPEndPoint remoteInternal = tmp.ReadIPEndpoint();
IPEndPoint remoteExternal = tmp.ReadIPEndpoint();
string token = tmp.ReadString();
bool isHost = (hostByte != 0);
LogDebug("NAT introduction received; we are designated " + (isHost ? "host" : "client"));
NetOutgoingMessage punch;
if (!isHost && m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.NatIntroductionSuccess) == false)
return; // no need to punch - we're not listening for nat intros!
// send internal punch
punch = CreateMessage(1);
punch.m_messageType = NetMessageType.NatPunchMessage;
punch.Write(hostByte);
punch.Write(token);
m_unsentUnconnectedMessages.Enqueue(new NetTuple<IPEndPoint, NetOutgoingMessage>(remoteInternal, punch));
// send external punch
punch = CreateMessage(1);
punch.m_messageType = NetMessageType.NatPunchMessage;
punch.Write(hostByte);
punch.Write(token);
m_unsentUnconnectedMessages.Enqueue(new NetTuple<IPEndPoint, NetOutgoingMessage>(remoteExternal, punch));
}
/// <summary>
/// Called when receiving a NatPunchMessage from a remote endpoint
/// </summary>
private void HandleNatPunch(int ptr, IPEndPoint senderEndpoint)
{
NetIncomingMessage tmp = SetupReadHelperMessage(ptr, 1000); // never mind length
byte fromHostByte = tmp.ReadByte();
if (fromHostByte == 0)
{
// it's from client
LogDebug("NAT punch received from " + senderEndpoint + " we're host, so we ignore this");
return; // don't alert hosts about nat punch successes; only clients
}
string token = tmp.ReadString();
LogDebug("NAT punch received from " + senderEndpoint + " we're client, so we've succeeded - token is " + token);
//
// Release punch success to client; enabling him to Connect() to msg.SenderIPEndPoint if token is ok
//
NetIncomingMessage punchSuccess = CreateIncomingMessage(NetIncomingMessageType.NatIntroductionSuccess, 10);
punchSuccess.m_senderEndpoint = senderEndpoint;
punchSuccess.Write(token);
ReleaseMessage(punchSuccess);
}
}
}

View File

@@ -0,0 +1,91 @@
/* Copyright (c) 2010 Michael Lidgren
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.
*/
using System;
using System.Reflection;
namespace Lidgren.Network
{
public partial class NetOutgoingMessage
{
/// <summary>
/// Writes all public and private declared instance fields of the object in alphabetical order using reflection
/// </summary>
public void WriteAllFields(object ob)
{
WriteAllFields(ob, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
}
/// <summary>
/// Writes all fields with specified binding in alphabetical order using reflection
/// </summary>
public void WriteAllFields(object ob, BindingFlags flags)
{
if (ob == null)
return;
Type tp = ob.GetType();
FieldInfo[] fields = tp.GetFields(flags);
NetUtility.SortMembersList(fields);
foreach (FieldInfo fi in fields)
{
object value = fi.GetValue(ob);
// find the appropriate Write method
MethodInfo writeMethod;
if (s_writeMethods.TryGetValue(fi.FieldType, out writeMethod))
writeMethod.Invoke(this, new object[] { value });
else
throw new NetException("Failed to find write method for type " + fi.FieldType);
}
}
/// <summary>
/// Writes all public and private declared instance properties of the object in alphabetical order using reflection
/// </summary>
public void WriteAllProperties(object ob)
{
WriteAllProperties(ob, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
}
/// <summary>
/// Writes all properties with specified binding in alphabetical order using reflection
/// </summary>
public void WriteAllProperties(object ob, BindingFlags flags)
{
if (ob == null)
return;
Type tp = ob.GetType();
PropertyInfo[] fields = tp.GetProperties(flags);
NetUtility.SortMembersList(fields);
foreach (PropertyInfo fi in fields)
{
MethodInfo getMethod = fi.GetGetMethod((flags & BindingFlags.NonPublic) == BindingFlags.NonPublic);
object value = getMethod.Invoke(ob, null);
// find the appropriate Write method
MethodInfo writeMethod;
if (s_writeMethods.TryGetValue(fi.PropertyType, out writeMethod))
writeMethod.Invoke(this, new object[] { value });
}
}
}
}

View File

@@ -0,0 +1,573 @@
/* Copyright (c) 2010 Michael Lidgren
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.
*/
using System;
using System.Collections.Generic;
using System.Net;
using System.Reflection;
using System.Text;
namespace Lidgren.Network
{
public sealed partial class NetOutgoingMessage
{
private const int c_overAllocateAmount = 4;
private static Dictionary<Type, MethodInfo> s_writeMethods;
internal byte[] m_data;
internal int m_bitLength;
static NetOutgoingMessage()
{
s_writeMethods = new Dictionary<Type, MethodInfo>();
MethodInfo[] methods = typeof(NetOutgoingMessage).GetMethods(BindingFlags.Instance | BindingFlags.Public);
foreach (MethodInfo mi in methods)
{
if (mi.Name.Equals("Write", StringComparison.InvariantCulture))
{
ParameterInfo[] pis = mi.GetParameters();
if (pis.Length == 1)
s_writeMethods[pis[0].ParameterType] = mi;
}
}
}
/// <summary>
/// Returns the internal data buffer, don't modify
/// </summary>
public byte[] PeekDataBuffer()
{
return m_data;
}
/// <summary>
/// Gets or sets the length of the buffer in bytes
/// </summary>
public int LengthBytes
{
get { return ((m_bitLength + 7) >> 3); }
set
{
m_bitLength = value * 8;
InternalEnsureBufferSize(m_bitLength);
}
}
/// <summary>
/// Gets or sets the length of the buffer in bits
/// </summary>
public int LengthBits
{
get { return m_bitLength; }
set
{
m_bitLength = value;
InternalEnsureBufferSize(m_bitLength);
}
}
/// <summary>
/// Ensures the buffer can hold this number of bits
/// </summary>
public void EnsureBufferSize(int numberOfBits)
{
int byteLen = ((numberOfBits + 7) >> 3);
if (m_data == null)
{
m_data = new byte[byteLen + c_overAllocateAmount];
return;
}
if (m_data.Length < byteLen)
Array.Resize<byte>(ref m_data, byteLen + c_overAllocateAmount);
return;
}
/// <summary>
/// Ensures the buffer can hold this number of bits
/// </summary>
public void InternalEnsureBufferSize(int numberOfBits)
{
int byteLen = ((numberOfBits + 7) >> 3);
if (m_data == null)
{
m_data = new byte[byteLen];
return;
}
if (m_data.Length < byteLen)
Array.Resize<byte>(ref m_data, byteLen);
return;
}
//
// 1 bit
//
public void Write(bool value)
{
EnsureBufferSize(m_bitLength + 1);
NetBitWriter.WriteByte((value ? (byte)1 : (byte)0), 1, m_data, m_bitLength);
m_bitLength += 1;
}
//
// 8 bit
//
public void Write(byte source)
{
EnsureBufferSize(m_bitLength + 8);
NetBitWriter.WriteByte(source, 8, m_data, m_bitLength);
m_bitLength += 8;
}
[CLSCompliant(false)]
public void Write(sbyte source)
{
EnsureBufferSize(m_bitLength + 8);
NetBitWriter.WriteByte((byte)source, 8, m_data, m_bitLength);
m_bitLength += 8;
}
public void Write(byte source, int numberOfBits)
{
NetException.Assert((numberOfBits > 0 && numberOfBits <= 8), "Write(byte, numberOfBits) can only write between 1 and 8 bits");
EnsureBufferSize(m_bitLength + numberOfBits);
NetBitWriter.WriteByte(source, numberOfBits, m_data, m_bitLength);
m_bitLength += numberOfBits;
}
public void Write(byte[] source)
{
if (source == null)
throw new ArgumentNullException("source");
int bits = source.Length * 8;
EnsureBufferSize(m_bitLength + bits);
NetBitWriter.WriteBytes(source, 0, source.Length, m_data, m_bitLength);
m_bitLength += bits;
}
public void Write(byte[] source, int offsetInBytes, int numberOfBytes)
{
if (source == null)
throw new ArgumentNullException("source");
int bits = numberOfBytes * 8;
EnsureBufferSize(m_bitLength + bits);
NetBitWriter.WriteBytes(source, offsetInBytes, numberOfBytes, m_data, m_bitLength);
m_bitLength += bits;
}
//
// 16 bit
//
[CLSCompliant(false)]
public void Write(UInt16 source)
{
EnsureBufferSize(m_bitLength + 16);
NetBitWriter.WriteUInt32((uint)source, 16, m_data, m_bitLength);
m_bitLength += 16;
}
[CLSCompliant(false)]
public void Write(UInt16 source, int numberOfBits)
{
NetException.Assert((numberOfBits > 0 && numberOfBits <= 16), "Write(ushort, numberOfBits) can only write between 1 and 16 bits");
EnsureBufferSize(m_bitLength + numberOfBits);
NetBitWriter.WriteUInt32((uint)source, numberOfBits, m_data, m_bitLength);
m_bitLength += numberOfBits;
}
public void Write(Int16 source)
{
EnsureBufferSize(m_bitLength + 16);
NetBitWriter.WriteUInt32((uint)source, 16, m_data, m_bitLength);
m_bitLength += 16;
}
//
// 32 bit
//
#if UNSAFE
public unsafe void Write(Int32 source)
{
EnsureBufferSize(m_bitLength + 32);
// can write fast?
if (m_bitLength % 8 == 0)
{
fixed (byte* numRef = &Data[m_bitLength / 8])
{
*((int*)numRef) = source;
}
}
else
{
NetBitWriter.WriteUInt32((UInt32)source, 32, Data, m_bitLength);
}
m_bitLength += 32;
}
#else
public void Write(Int32 source)
{
EnsureBufferSize(m_bitLength + 32);
NetBitWriter.WriteUInt32((UInt32)source, 32, m_data, m_bitLength);
m_bitLength += 32;
}
#endif
#if UNSAFE
public unsafe void Write(UInt32 source)
{
EnsureBufferSize(m_bitLength + 32);
// can write fast?
if (m_bitLength % 8 == 0)
{
fixed (byte* numRef = &Data[m_bitLength / 8])
{
*((uint*)numRef) = source;
}
}
else
{
NetBitWriter.WriteUInt32(source, 32, Data, m_bitLength);
}
m_bitLength += 32;
}
#else
[CLSCompliant(false)]
public void Write(UInt32 source)
{
EnsureBufferSize(m_bitLength + 32);
NetBitWriter.WriteUInt32(source, 32, m_data, m_bitLength);
m_bitLength += 32;
}
#endif
[CLSCompliant(false)]
public void Write(UInt32 source, int numberOfBits)
{
NetException.Assert((numberOfBits > 0 && numberOfBits <= 32), "Write(uint, numberOfBits) can only write between 1 and 32 bits");
EnsureBufferSize(m_bitLength + numberOfBits);
NetBitWriter.WriteUInt32(source, numberOfBits, m_data, m_bitLength);
m_bitLength += numberOfBits;
}
public void Write(Int32 source, int numberOfBits)
{
NetException.Assert((numberOfBits > 0 && numberOfBits <= 32), "Write(int, numberOfBits) can only write between 1 and 32 bits");
EnsureBufferSize(m_bitLength + numberOfBits);
if (numberOfBits != 32)
{
// make first bit sign
int signBit = 1 << (numberOfBits - 1);
if (source < 0)
source = (-source - 1) | signBit;
else
source &= (~signBit);
}
NetBitWriter.WriteUInt32((uint)source, numberOfBits, m_data, m_bitLength);
m_bitLength += numberOfBits;
}
//
// 64 bit
//
[CLSCompliant(false)]
public void Write(UInt64 source)
{
EnsureBufferSize(m_bitLength + 64);
NetBitWriter.WriteUInt64(source, 64, m_data, m_bitLength);
m_bitLength += 64;
}
[CLSCompliant(false)]
public void Write(UInt64 source, int numberOfBits)
{
EnsureBufferSize(m_bitLength + numberOfBits);
NetBitWriter.WriteUInt64(source, numberOfBits, m_data, m_bitLength);
m_bitLength += numberOfBits;
}
public void Write(Int64 source)
{
EnsureBufferSize(m_bitLength + 64);
ulong usource = (ulong)source;
NetBitWriter.WriteUInt64(usource, 64, m_data, m_bitLength);
m_bitLength += 64;
}
public void Write(Int64 source, int numberOfBits)
{
EnsureBufferSize(m_bitLength + numberOfBits);
ulong usource = (ulong)source;
NetBitWriter.WriteUInt64(usource, numberOfBits, m_data, m_bitLength);
m_bitLength += numberOfBits;
}
//
// Floating point
//
#if UNSAFE
public unsafe void Write(float source)
{
uint val = *((uint*)&source);
#if BIGENDIAN
val = NetUtility.SwapByteOrder(val);
#endif
Write(val);
}
#else
public void Write(float source)
{
byte[] val = BitConverter.GetBytes(source);
#if BIGENDIAN
// swap byte order
byte tmp = val[3];
val[3] = val[0];
val[0] = tmp;
tmp = val[2];
val[2] = val[1];
val[1] = tmp;
#endif
Write(val);
}
#endif
#if UNSAFE
public unsafe void Write(double source)
{
ulong val = *((ulong*)&source);
#if BIGENDIAN
val = NetUtility.SwapByteOrder(val);
#endif
Write(val);
}
#else
public void Write(double source)
{
byte[] val = BitConverter.GetBytes(source);
#if BIGENDIAN
// 0 1 2 3 4 5 6 7
// swap byte order
byte tmp = val[7];
val[7] = val[0];
val[0] = tmp;
tmp = val[6];
val[6] = val[1];
val[1] = tmp;
tmp = val[5];
val[5] = val[2];
val[2] = tmp;
tmp = val[4];
val[4] = val[3];
val[3] = tmp;
#endif
Write(val);
}
#endif
//
// Variable bits
//
/// <summary>
/// Write Base128 encoded variable sized unsigned integer
/// </summary>
/// <returns>number of bytes written</returns>
[CLSCompliant(false)]
public int WriteVariableUInt32(uint value)
{
int retval = 1;
uint num1 = (uint)value;
while (num1 >= 0x80)
{
this.Write((byte)(num1 | 0x80));
num1 = num1 >> 7;
retval++;
}
this.Write((byte)num1);
return retval;
}
/// <summary>
/// Write Base128 encoded variable sized signed integer
/// </summary>
/// <returns>number of bytes written</returns>
public int WriteVariableInt32(int value)
{
int retval = 1;
uint num1 = (uint)((value << 1) ^ (value >> 31));
while (num1 >= 0x80)
{
this.Write((byte)(num1 | 0x80));
num1 = num1 >> 7;
retval++;
}
this.Write((byte)num1);
return retval;
}
/// <summary>
/// Write Base128 encoded variable sized unsigned integer
/// </summary>
/// <returns>number of bytes written</returns>
[CLSCompliant(false)]
public int WriteVariableUInt64(UInt64 value)
{
int retval = 1;
UInt64 num1 = (UInt64)value;
while (num1 >= 0x80)
{
this.Write((byte)(num1 | 0x80));
num1 = num1 >> 7;
retval++;
}
this.Write((byte)num1);
return retval;
}
/// <summary>
/// Compress (lossy) a float in the range -1..1 using numberOfBits bits
/// </summary>
public void WriteSignedSingle(float value, int numberOfBits)
{
NetException.Assert(((value >= -1.0) && (value <= 1.0)), " WriteSignedSingle() must be passed a float in the range -1 to 1; val is " + value);
float unit = (value + 1.0f) * 0.5f;
int maxVal = (1 << numberOfBits) - 1;
uint writeVal = (uint)(unit * (float)maxVal);
Write(writeVal, numberOfBits);
}
/// <summary>
/// Compress (lossy) a float in the range 0..1 using numberOfBits bits
/// </summary>
public void WriteUnitSingle(float value, int numberOfBits)
{
NetException.Assert(((value >= 0.0) && (value <= 1.0)), " WriteUnitSingle() must be passed a float in the range 0 to 1; val is " + value);
int maxValue = (1 << numberOfBits) - 1;
uint writeVal = (uint)(value * (float)maxValue);
Write(writeVal, numberOfBits);
}
/// <summary>
/// Compress a float within a specified range using a certain number of bits
/// </summary>
public void WriteRangedSingle(float value, float min, float max, int numberOfBits)
{
NetException.Assert(((value >= min) && (value <= max)), " WriteRangedSingle() must be passed a float in the range MIN to MAX; val is " + value);
float range = max - min;
float unit = ((value - min) / range);
int maxVal = (1 << numberOfBits) - 1;
Write((UInt32)((float)maxVal * unit), numberOfBits);
}
/// <summary>
/// Writes an integer with the least amount of bits need for the specified range
/// Returns number of bits written
/// </summary>
public int WriteRangedInteger(int min, int max, int value)
{
NetException.Assert(value >= min && value <= max, "Value not within min/max range!");
uint range = (uint)(max - min);
int numBits = NetUtility.BitsToHoldUInt(range);
uint rvalue = (uint)(value - min);
Write(rvalue, numBits);
return numBits;
}
/// <summary>
/// Write a string
/// </summary>
public void Write(string source)
{
if (string.IsNullOrEmpty(source))
{
EnsureBufferSize(m_bitLength + 8);
WriteVariableUInt32(0);
return;
}
byte[] bytes = Encoding.UTF8.GetBytes(source);
EnsureBufferSize(m_bitLength + 8 + (bytes.Length * 8));
WriteVariableUInt32((uint)bytes.Length);
Write(bytes);
}
/// <summary>
/// Writes an endpoint description
/// </summary>
public void Write(IPEndPoint endPoint)
{
byte[] bytes = endPoint.Address.GetAddressBytes();
Write((byte)bytes.Length);
Write(bytes);
Write((ushort)endPoint.Port);
}
/// <summary>
/// Pads data with enough bits to reach a full byte. Decreases cpu usage for subsequent byte writes.
/// </summary>
public void WritePadBits()
{
m_bitLength = ((m_bitLength + 7) >> 3) * 8;
EnsureBufferSize(m_bitLength);
}
/// <summary>
/// Pads data with the specified number of bits.
/// </summary>
public void WritePadBits(int numberOfBits)
{
m_bitLength += numberOfBits;
EnsureBufferSize(m_bitLength);
}
/// <summary>
/// Append all the bits of message to this message
/// </summary>
public void Write(NetOutgoingMessage message)
{
EnsureBufferSize(m_bitLength + (message.LengthBytes * 8));
Write(message.m_data, 0, message.LengthBytes);
// did we write excessive bits?
int bitsInLastByte = (message.m_bitLength % 8);
if (bitsInLastByte != 0)
{
int excessBits = 8 - bitsInLastByte;
m_bitLength -= excessBits;
}
}
}
}

View File

@@ -0,0 +1,144 @@
/* Copyright (c) 2010 Michael Lidgren
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.
*/
using System;
using System.Diagnostics;
namespace Lidgren.Network
{
/// <summary>
/// Outgoing message used to send data to remote peer(s)
/// </summary>
[DebuggerDisplay("LengthBits={LengthBits}")]
public sealed partial class NetOutgoingMessage
{
internal NetMessageType m_messageType;
internal bool m_isSent;
internal int m_recyclingCount;
internal int m_fragmentGroup; // which group of fragments ths belongs to
internal int m_fragmentGroupTotalBits; // total number of bits in this group
internal int m_fragmentChunkByteSize; // size, in bytes, of every chunk but the last one
internal int m_fragmentChunkNumber; // which number chunk this is, starting with 0
internal NetOutgoingMessage()
{
}
internal void Reset()
{
m_messageType = NetMessageType.LibraryError;
m_bitLength = 0;
m_isSent = false;
m_recyclingCount = 0;
m_fragmentGroup = 0;
}
internal int Encode(byte[] intoBuffer, int ptr, int sequenceNumber)
{
// 8 bits - NetMessageType
// 1 bit - Fragment?
// 15 bits - Sequence number
// 16 bits - Payload length in bits
intoBuffer[ptr++] = (byte)m_messageType;
byte low = (byte)((sequenceNumber << 1) | (m_fragmentGroup == 0 ? 0 : 1));
intoBuffer[ptr++] = low;
intoBuffer[ptr++] = (byte)(sequenceNumber >> 7);
if (m_fragmentGroup == 0)
{
intoBuffer[ptr++] = (byte)m_bitLength;
intoBuffer[ptr++] = (byte)(m_bitLength >> 8);
int byteLen = NetUtility.BytesToHoldBits(m_bitLength);
if (byteLen > 0)
{
Buffer.BlockCopy(m_data, 0, intoBuffer, ptr, byteLen);
ptr += byteLen;
}
}
else
{
int wasPtr = ptr;
intoBuffer[ptr++] = (byte)m_bitLength;
intoBuffer[ptr++] = (byte)(m_bitLength >> 8);
//
// write fragmentation header
//
ptr = NetFragmentationHelper.WriteHeader(intoBuffer, ptr, m_fragmentGroup, m_fragmentGroupTotalBits, m_fragmentChunkByteSize, m_fragmentChunkNumber);
int hdrLen = ptr - wasPtr - 2;
// update length
int realBitLength = m_bitLength + (hdrLen * 8);
intoBuffer[wasPtr] = (byte)realBitLength;
intoBuffer[wasPtr + 1] = (byte)(realBitLength >> 8);
int byteLen = NetUtility.BytesToHoldBits(m_bitLength);
if (byteLen > 0)
{
Buffer.BlockCopy(m_data, (int)(m_fragmentChunkNumber * m_fragmentChunkByteSize), intoBuffer, ptr, byteLen);
ptr += byteLen;
}
}
NetException.Assert(ptr > 0);
return ptr;
}
internal int GetEncodedSize()
{
int retval = 5; // regular headers
if (m_fragmentGroup != 0)
retval += NetFragmentationHelper.GetFragmentationHeaderSize(m_fragmentGroup, m_fragmentGroupTotalBits / 8, m_fragmentChunkByteSize, m_fragmentChunkNumber);
retval += this.LengthBytes;
return retval;
}
/// <summary>
/// Encrypt this message using the XTEA algorithm; no more writing can be done before sending it
/// </summary>
public void Encrypt(NetXtea tea)
{
// need blocks of 8 bytes
WritePadBits();
int blocksNeeded = (m_bitLength + 63) / 64;
int missingBits = (blocksNeeded * 64) - m_bitLength;
int missingBytes = NetUtility.BytesToHoldBits(missingBits);
for (int i = 0; i < missingBytes; i++)
Write((byte)0);
byte[] result = new byte[m_data.Length];
for (int i = 0; i < blocksNeeded; i++)
tea.EncryptBlock(m_data, (i * 8), result, (i * 8));
m_data = result;
}
/// <summary>
/// Returns a string that represents this object
/// </summary>
public override string ToString()
{
return "[NetOutgoingMessage " + m_messageType + " " + this.LengthBytes + " bytes]";
}
}
}

View File

@@ -0,0 +1,91 @@
/* Copyright (c) 2010 Michael Lidgren
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.
*/
using System.Collections.Generic;
using System;
namespace Lidgren.Network
{
internal enum PendingConnectionStatus
{
NotPending = 0,
Pending,
Approved,
Denied,
}
public partial class NetPeer
{
private List<NetConnection> m_pendingConnections;
private void AddPendingConnection(NetConnection conn, NetIncomingMessage approval)
{
if (m_pendingConnections == null)
m_pendingConnections = new List<NetConnection>();
m_pendingConnections.Add(conn);
conn.m_pendingStatus = PendingConnectionStatus.Pending;
if (approval == null)
approval = CreateIncomingMessage(NetIncomingMessageType.ConnectionApproval, 0);
approval.m_messageType = NetMessageType.Library;
approval.m_senderConnection = conn;
approval.m_senderEndpoint = conn.m_remoteEndpoint;
ReleaseMessage(approval);
}
private void CheckPendingConnections()
{
if (m_pendingConnections == null || m_pendingConnections.Count < 1)
return;
foreach (NetConnection conn in m_pendingConnections)
{
switch (conn.m_pendingStatus)
{
case PendingConnectionStatus.Pending:
if (NetTime.Now > conn.m_connectInitationTime + 10.0)
{
LogWarning("Pending connection still in pending state after 10 seconds; forgot to Approve/Deny?");
m_pendingConnections.Remove(conn);
if (m_pendingConnections.Count < 1)
m_pendingConnections = null;
return;
}
break;
case PendingConnectionStatus.Approved:
// accept connection
AcceptConnection(conn);
m_pendingConnections.Remove(conn);
if (m_pendingConnections.Count < 1)
m_pendingConnections = null;
return;
case PendingConnectionStatus.Denied:
// send disconnected
NetOutgoingMessage bye = CreateLibraryMessage(NetMessageLibraryType.Disconnect, conn.m_pendingDenialReason);
SendUnconnectedLibrary(bye, conn.m_remoteEndpoint);
m_pendingConnections.Remove(conn);
if (m_pendingConnections.Count < 1)
m_pendingConnections = null;
return;
}
}
}
}
}

View File

@@ -0,0 +1,57 @@
using System;
using System.Net;
namespace Lidgren.Network
{
public partial class NetPeer
{
/// <summary>
/// Emit a discovery signal to all hosts on your subnet
/// </summary>
public void DiscoverLocalPeers(int serverPort)
{
NetOutgoingMessage om = CreateMessage(0);
om.m_messageType = NetMessageType.Discovery;
m_unsentUnconnectedMessages.Enqueue(new NetTuple<IPEndPoint, NetOutgoingMessage>(new IPEndPoint(IPAddress.Broadcast, serverPort), om));
}
/// <summary>
/// Emit a discovery signal to a single known host
/// </summary>
public bool DiscoverKnownPeer(string host, int serverPort)
{
IPAddress address = NetUtility.Resolve(host);
if (address == null)
return false;
DiscoverKnownPeer(new IPEndPoint(address, serverPort));
return true;
}
/// <summary>
/// Emit a discovery signal to a single known host
/// </summary>
public void DiscoverKnownPeer(IPEndPoint endpoint)
{
NetOutgoingMessage om = CreateMessage(0);
om.m_messageType = NetMessageType.Discovery;
m_unsentUnconnectedMessages.Enqueue(new NetTuple<IPEndPoint, NetOutgoingMessage>(endpoint, om));
}
/// <summary>
/// Send a discovery response message
/// </summary>
public void SendDiscoveryResponse(NetOutgoingMessage msg, IPEndPoint recipient)
{
if (recipient == null)
throw new ArgumentNullException("recipient");
if (msg == null)
msg = CreateMessage(0);
else if (msg.m_isSent)
throw new NetException("Message has already been sent!");
msg.m_messageType = NetMessageType.DiscoveryResponse;
m_unsentUnconnectedMessages.Enqueue(new NetTuple<IPEndPoint, NetOutgoingMessage>(recipient, msg));
}
}
}

View File

@@ -0,0 +1,150 @@
using System;
using System.Threading;
using System.Collections.Generic;
namespace Lidgren.Network
{
internal class ReceivedFragmentGroup
{
public float LastReceived;
public byte[] Data;
public NetBitVector ReceivedChunks;
}
public partial class NetPeer
{
private int m_lastUsedFragmentGroup;
private Dictionary<int, ReceivedFragmentGroup> m_receivedFragmentGroups;
// on user thread
private void SendFragmentedMessage(NetOutgoingMessage msg, IList<NetConnection> recipients, NetDeliveryMethod method, int sequenceChannel)
{
int group = Interlocked.Increment(ref m_lastUsedFragmentGroup);
if (group >= NetConstants.MaxFragmentationGroups)
{
// TODO: not thread safe; but in practice probably not an issue
m_lastUsedFragmentGroup = 1;
group = 1;
}
msg.m_fragmentGroup = group;
// do not send msg; but set fragmentgroup in case user tries to recycle it immediately
// create fragmentation specifics
int totalBytes = msg.LengthBytes;
int mtu = m_configuration.MaximumTransmissionUnit;
int bytesPerChunk = NetFragmentationHelper.GetBestChunkSize(group, totalBytes, mtu);
int numChunks = totalBytes / bytesPerChunk;
if (numChunks * bytesPerChunk < totalBytes)
numChunks++;
int bitsPerChunk = bytesPerChunk * 8;
int bitsLeft = msg.LengthBits;
for (int i = 0; i < numChunks; i++)
{
NetOutgoingMessage chunk = CreateMessage(mtu);
chunk.m_bitLength = (bitsLeft > bitsPerChunk ? bitsPerChunk : bitsLeft);
chunk.m_data = msg.m_data;
chunk.m_fragmentGroup = group;
chunk.m_fragmentGroupTotalBits = totalBytes * 8;
chunk.m_fragmentChunkByteSize = bytesPerChunk;
chunk.m_fragmentChunkNumber = i;
NetException.Assert(chunk.m_bitLength != 0);
NetException.Assert(chunk.GetEncodedSize() < mtu);
Interlocked.Add(ref chunk.m_recyclingCount, recipients.Count);
foreach (NetConnection recipient in recipients)
recipient.EnqueueMessage(chunk, method, sequenceChannel);
bitsLeft -= bitsPerChunk;
}
return;
}
private void HandleReleasedFragment(NetIncomingMessage im)
{
//
// read fragmentation header and combine fragments
//
int group;
int totalBits;
int chunkByteSize;
int chunkNumber;
int ptr = NetFragmentationHelper.ReadHeader(
im.m_data, 0,
out group,
out totalBits,
out chunkByteSize,
out chunkNumber
);
NetException.Assert(im.LengthBytes > ptr);
NetException.Assert(group > 0);
NetException.Assert(totalBits > 0);
NetException.Assert(chunkByteSize > 0);
int totalBytes = NetUtility.BytesToHoldBits((int)totalBits);
int totalNumChunks = totalBytes / chunkByteSize;
if (totalNumChunks * chunkByteSize < totalBytes)
totalNumChunks++;
NetException.Assert(chunkNumber < totalNumChunks);
if (chunkNumber >= totalNumChunks)
{
LogWarning("Index out of bounds for chunk " + chunkNumber + " (total chunks " + totalNumChunks + ")");
return;
}
ReceivedFragmentGroup info;
if (!m_receivedFragmentGroups.TryGetValue(group, out info))
{
info = new ReceivedFragmentGroup();
info.Data = new byte[totalBytes];
info.ReceivedChunks = new NetBitVector(totalNumChunks);
m_receivedFragmentGroups[group] = info;
}
info.ReceivedChunks[chunkNumber] = true;
info.LastReceived = (float)NetTime.Now;
// copy to data
int offset = (chunkNumber * chunkByteSize);
Buffer.BlockCopy(im.m_data, ptr, info.Data, offset, im.LengthBytes - ptr);
int cnt = info.ReceivedChunks.Count();
//Console.WriteLine("Found fragment #" + chunkNumber + " in group " + group + " offset " + offset + " of total bits " + totalBits + " (total chunks done " + cnt + ")");
LogVerbose("Received fragment " + chunkNumber + " of " + totalNumChunks + " (" + cnt + " chunks received)");
if (info.ReceivedChunks.Count() == totalNumChunks)
{
// Done! Transform this incoming message
im.m_data = info.Data;
im.m_bitLength = (int)totalBits;
im.m_isFragment = false;
LogVerbose("Fragment group #" + group + " fully received in " + totalNumChunks + " chunks (" + totalBits + " bits)");
ReleaseMessage(im);
}
else
{
// data has been copied; recycle this incoming message
Recycle(im);
}
return;
}
}
}

View File

@@ -0,0 +1,489 @@
#define IS_MAC_AVAILABLE
using System;
using System.Net;
using System.Threading;
using System.Diagnostics;
using System.Security.Cryptography;
using System.Net.Sockets;
using System.Collections.Generic;
namespace Lidgren.Network
{
public partial class NetPeer
{
private NetPeerStatus m_status;
private Thread m_networkThread;
private Socket m_socket;
internal byte[] m_sendBuffer;
internal byte[] m_receiveBuffer;
internal NetIncomingMessage m_readHelperMessage;
private EndPoint m_senderRemote;
private object m_initializeLock = new object();
private uint m_frameCounter;
internal readonly NetPeerConfiguration m_configuration;
private readonly NetQueue<NetIncomingMessage> m_releasedIncomingMessages;
internal readonly NetQueue<NetTuple<IPEndPoint, NetOutgoingMessage>> m_unsentUnconnectedMessages;
internal Dictionary<IPEndPoint, NetConnection> m_handshakes;
internal readonly NetPeerStatistics m_statistics;
internal long m_uniqueIdentifier;
private AutoResetEvent m_messageReceivedEvent = new AutoResetEvent(false);
internal void ReleaseMessage(NetIncomingMessage msg)
{
NetException.Assert(msg.m_incomingMessageType != NetIncomingMessageType.Error);
if (msg.m_isFragment)
{
HandleReleasedFragment(msg);
return;
}
m_releasedIncomingMessages.Enqueue(msg);
if (m_messageReceivedEvent != null)
m_messageReceivedEvent.Set();
}
private void InitializeNetwork()
{
VerifyNetworkThread();
lock (m_initializeLock)
{
m_configuration.Lock();
if (m_status == NetPeerStatus.Running)
return;
InitializePools();
m_releasedIncomingMessages.Clear();
m_unsentUnconnectedMessages.Clear();
m_handshakes.Clear();
// bind to socket
IPEndPoint iep = null;
iep = new IPEndPoint(m_configuration.LocalAddress, m_configuration.Port);
EndPoint ep = (EndPoint)iep;
m_socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
m_socket.ReceiveBufferSize = m_configuration.ReceiveBufferSize;
m_socket.SendBufferSize = m_configuration.SendBufferSize;
m_socket.Blocking = false;
m_socket.Bind(ep);
IPEndPoint boundEp = m_socket.LocalEndPoint as IPEndPoint;
LogDebug("Socket bound to " + boundEp + ": " + m_socket.IsBound);
m_listenPort = boundEp.Port;
m_receiveBuffer = new byte[m_configuration.ReceiveBufferSize];
m_sendBuffer = new byte[m_configuration.SendBufferSize];
m_readHelperMessage = new NetIncomingMessage(NetIncomingMessageType.Error);
m_readHelperMessage.m_data = m_receiveBuffer;
byte[] macBytes = new byte[8];
NetRandom.Instance.NextBytes(macBytes);
#if IS_MAC_AVAILABLE
System.Net.NetworkInformation.PhysicalAddress pa = NetUtility.GetMacAddress();
if (pa != null)
{
macBytes = pa.GetAddressBytes();
LogVerbose("Mac address is " + NetUtility.ToHexString(macBytes));
}
else
{
LogWarning("Failed to get Mac address");
}
#endif
byte[] epBytes = BitConverter.GetBytes(boundEp.GetHashCode());
byte[] combined = new byte[epBytes.Length + macBytes.Length];
Array.Copy(epBytes, 0, combined, 0, epBytes.Length);
Array.Copy(macBytes, 0, combined, epBytes.Length, macBytes.Length);
m_uniqueIdentifier = BitConverter.ToInt64(SHA1.Create().ComputeHash(combined), 0);
m_status = NetPeerStatus.Running;
}
}
private void NetworkLoop()
{
VerifyNetworkThread();
LogDebug("Network thread started");
//
// Network loop
//
do
{
try
{
Heartbeat();
}
catch (Exception ex)
{
LogWarning(ex.ToString());
}
} while (m_status == NetPeerStatus.Running);
//
// perform shutdown
//
ExecutePeerShutdown();
}
private void ExecutePeerShutdown()
{
VerifyNetworkThread();
LogDebug("Shutting down...");
// disconnect and make one final heartbeat
lock (m_connections)
{
foreach (NetConnection conn in m_connections)
conn.Shutdown(m_shutdownReason);
}
lock (m_handshakes)
{
foreach (NetConnection conn in m_handshakes.Values)
conn.Shutdown(m_shutdownReason);
}
// one final heartbeat, will send stuff and do disconnect
Heartbeat();
lock (m_initializeLock)
{
try
{
if (m_socket != null)
{
m_socket.Shutdown(SocketShutdown.Receive);
m_socket.Close(2); // 2 seconds timeout
}
if (m_messageReceivedEvent != null)
{
m_messageReceivedEvent.Close();
m_messageReceivedEvent = null;
}
}
finally
{
m_socket = null;
m_status = NetPeerStatus.NotRunning;
LogDebug("Shutdown complete");
}
m_receiveBuffer = null;
m_sendBuffer = null;
m_unsentUnconnectedMessages.Clear();
m_connections.Clear();
m_handshakes.Clear();
}
return;
}
private void Heartbeat()
{
VerifyNetworkThread();
float now = (float)NetTime.Now;
m_frameCounter++;
// do handshake heartbeats
if ((m_frameCounter % 3) == 0)
{
foreach (NetConnection conn in m_handshakes.Values)
{
conn.UnconnectedHeartbeat(now);
if (conn.m_status == NetConnectionStatus.Connected || conn.m_status == NetConnectionStatus.Disconnected)
break; // collection has been modified
}
}
#if DEBUG
SendDelayedPackets();
#endif
// do connection heartbeats
lock (m_connections)
{
foreach (NetConnection conn in m_connections)
{
conn.Heartbeat(now, m_frameCounter);
if (conn.m_status == NetConnectionStatus.Disconnected)
{
//
// remove connection
//
m_connections.Remove(conn);
m_connectionLookup.Remove(conn.RemoteEndpoint);
break; // can't continue iteration here
}
}
}
// send unsent unconnected messages
NetTuple<IPEndPoint, NetOutgoingMessage> unsent;
while (m_unsentUnconnectedMessages.TryDequeue(out unsent))
{
NetOutgoingMessage om = unsent.Item2;
bool connReset;
int len = om.Encode(m_sendBuffer, 0, 0);
SendPacket(len, unsent.Item1, 1, out connReset);
Interlocked.Decrement(ref om.m_recyclingCount);
if (om.m_recyclingCount <= 0)
Recycle(om);
}
//
// read from socket
//
if (m_socket == null)
return;
if (!m_socket.Poll(1000, SelectMode.SelectRead)) // wait up to 1 ms for data to arrive
return;
//if (m_socket == null || m_socket.Available < 1)
// return;
int bytesReceived = 0;
try
{
bytesReceived = m_socket.ReceiveFrom(m_receiveBuffer, 0, m_receiveBuffer.Length, SocketFlags.None, ref m_senderRemote);
}
catch (SocketException sx)
{
if (sx.SocketErrorCode == SocketError.ConnectionReset)
{
// connection reset by peer, aka connection forcibly closed aka "ICMP port unreachable"
// we should shut down the connection; but m_senderRemote seemingly cannot be trusted, so which connection should we shut down?!
// So, what to do?
return;
}
LogWarning(sx.ToString());
return;
}
if (bytesReceived < NetConstants.HeaderByteSize)
return;
//LogVerbose("Received " + bytesReceived + " bytes");
IPEndPoint ipsender = (IPEndPoint)m_senderRemote;
NetConnection sender = null;
m_connectionLookup.TryGetValue(ipsender, out sender);
//
// parse packet into messages
//
int ptr = 0;
while ((bytesReceived - ptr) >= NetConstants.HeaderByteSize)
{
// decode header
// 8 bits - NetMessageType
// 1 bit - Fragment?
// 15 bits - Sequence number
// 16 bits - Payload length in bits
NetMessageType tp = (NetMessageType)m_receiveBuffer[ptr++];
byte low = m_receiveBuffer[ptr++];
byte high = m_receiveBuffer[ptr++];
bool isFragment = ((low & 1) == 1);
ushort sequenceNumber = (ushort)((low >> 1) | (((int)high) << 7));
ushort payloadBitLength = (ushort)(m_receiveBuffer[ptr++] | (m_receiveBuffer[ptr++] << 8));
int payloadByteLength = NetUtility.BytesToHoldBits(payloadBitLength);
if (bytesReceived - ptr < payloadByteLength)
{
LogWarning("Malformed packet; stated payload length " + payloadByteLength + ", remaining bytes " + (bytesReceived - ptr));
return;
}
try
{
NetException.Assert(tp < NetMessageType.Unused1 || tp > NetMessageType.Unused29);
if (tp >= NetMessageType.LibraryError)
{
if (sender != null)
sender.ReceivedLibraryMessage(tp, ptr, payloadByteLength);
else
ReceivedUnconnectedLibraryMessage(ipsender, tp, ptr, payloadByteLength);
}
else
{
if (sender == null && !m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.UnconnectedData))
return; // dropping unconnected message since it's not enabled
NetIncomingMessage msg = CreateIncomingMessage(NetIncomingMessageType.Data, payloadByteLength);
msg.m_isFragment = isFragment;
msg.m_sequenceNumber = sequenceNumber;
msg.m_receivedMessageType = tp;
msg.m_senderConnection = sender;
msg.m_senderEndpoint = ipsender;
msg.m_bitLength = payloadBitLength;
Buffer.BlockCopy(m_receiveBuffer, ptr, msg.m_data, 0, payloadByteLength);
if (sender != null)
{
if (tp == NetMessageType.Unconnected)
{
// We're connected; but we can still send unconnected messages to this peer
msg.m_incomingMessageType = NetIncomingMessageType.UnconnectedData;
ReleaseMessage(msg);
}
else
{
// connected application (non-library) message
sender.ReceivedMessage(msg);
}
}
else
{
// at this point we know the message type is enabled
// unconnected application (non-library) message
msg.m_incomingMessageType = NetIncomingMessageType.UnconnectedData;
ReleaseMessage(msg);
}
}
}
catch (Exception ex)
{
LogError("Packet parsing error: " + ex.Message + " from " + ipsender);
}
ptr += payloadByteLength;
}
}
private void ReceivedUnconnectedLibraryMessage(IPEndPoint senderEndpoint, NetMessageType tp, int ptr, int payloadByteLength)
{
NetConnection shake;
if (m_handshakes.TryGetValue(senderEndpoint, out shake))
{
shake.ReceivedHandshake(tp, ptr, payloadByteLength);
return;
}
//
// Library message from a completely unknown sender; lets just accept Connect
//
switch (tp)
{
case NetMessageType.Discovery:
if (m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.DiscoveryRequest))
{
NetIncomingMessage dm = CreateIncomingMessage(NetIncomingMessageType.DiscoveryRequest, payloadByteLength);
if (payloadByteLength > 0)
Buffer.BlockCopy(m_receiveBuffer, ptr, dm.m_data, 0, payloadByteLength);
dm.m_bitLength = payloadByteLength * 8;
dm.m_senderEndpoint = senderEndpoint;
ReleaseMessage(dm);
}
return;
case NetMessageType.DiscoveryResponse:
if (m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.DiscoveryResponse))
{
NetIncomingMessage dr = CreateIncomingMessage(NetIncomingMessageType.DiscoveryResponse, payloadByteLength);
if (payloadByteLength > 0)
Buffer.BlockCopy(m_receiveBuffer, ptr, dr.m_data, 0, payloadByteLength);
dr.m_bitLength = payloadByteLength * 8;
dr.m_senderEndpoint = senderEndpoint;
ReleaseMessage(dr);
}
return;
case NetMessageType.NatIntroduction:
HandleNatIntroduction(ptr);
return;
case NetMessageType.NatPunchMessage:
HandleNatPunch(ptr, senderEndpoint);
return;
case NetMessageType.Connect:
// proceed
break;
case NetMessageType.Disconnect:
// this is probably ok
LogVerbose("Received Disconnect from unconnected source: " + senderEndpoint);
return;
default:
LogWarning("Received unhandled library message " + tp + " from " + senderEndpoint);
return;
}
// It's someone wanting to shake hands with us!
int reservedSlots = m_handshakes.Count + m_connections.Count;
if (reservedSlots >= m_configuration.m_maximumConnections)
{
// server full
NetOutgoingMessage full = CreateMessage("Server full");
full.m_messageType = NetMessageType.Disconnect;
SendLibrary(full, senderEndpoint);
return;
}
// Ok, start handshake!
NetConnection conn = new NetConnection(this, senderEndpoint);
m_handshakes.Add(senderEndpoint, conn);
conn.ReceivedHandshake(tp, ptr, payloadByteLength);
return;
}
internal void AcceptConnection(NetConnection conn)
{
// LogDebug("Accepted connection " + conn);
if (m_handshakes.Remove(conn.m_remoteEndpoint) == false)
LogWarning("AcceptConnection called but m_handshakes did not contain it!");
lock (m_connections)
{
if (m_connections.Contains(conn))
{
LogWarning("AcceptConnection called but m_connection already contains it!");
}
else
{
m_connections.Add(conn);
m_connectionLookup.Add(conn.m_remoteEndpoint, conn);
}
}
}
[Conditional("DEBUG")]
internal void VerifyNetworkThread()
{
Thread ct = Thread.CurrentThread;
if (Thread.CurrentThread != m_networkThread)
throw new NetException("Executing on wrong thread! Should be library system thread (is " + ct.Name + " mId " + ct.ManagedThreadId + ")");
}
internal NetIncomingMessage SetupReadHelperMessage(int ptr, int payloadLength)
{
VerifyNetworkThread();
m_readHelperMessage.m_bitLength = (ptr + payloadLength) * 8;
m_readHelperMessage.m_readPosition = (ptr * 8);
return m_readHelperMessage;
}
}
}

View File

@@ -0,0 +1,204 @@
/* Copyright (c) 2010 Michael Lidgren
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.
*/
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Diagnostics;
namespace Lidgren.Network
{
public partial class NetPeer
{
#if DEBUG
private readonly List<DelayedPacket> m_delayedPackets = new List<DelayedPacket>();
private class DelayedPacket
{
public byte[] Data;
public double DelayedUntil;
public IPEndPoint Target;
}
internal void SendPacket(int numBytes, IPEndPoint target, int numMessages, out bool connectionReset)
{
connectionReset = false;
// simulate loss
float loss = m_configuration.m_loss;
if (loss > 0.0f)
{
if ((float)NetRandom.Instance.NextDouble() < loss)
{
LogVerbose("Sending packet " + numBytes + " bytes - SIMULATED LOST!");
return; // packet "lost"
}
}
m_statistics.PacketSent(numBytes, numMessages);
// simulate latency
float m = m_configuration.m_minimumOneWayLatency;
float r = m_configuration.m_randomOneWayLatency;
if (m == 0.0f && r == 0.0f)
{
// no latency simulation
// LogVerbose("Sending packet " + numBytes + " bytes");
bool wasSent = ActuallySendPacket(m_sendBuffer, numBytes, target, out connectionReset);
// TODO: handle wasSent == false?
return;
}
int num = 1;
if (m_configuration.m_duplicates > 0.0f && NetRandom.Instance.NextSingle() < m_configuration.m_duplicates)
num++;
float delay = 0;
for (int i = 0; i < num; i++)
{
delay = m_configuration.m_minimumOneWayLatency + (NetRandom.Instance.NextSingle() * m_configuration.m_randomOneWayLatency);
// Enqueue delayed packet
DelayedPacket p = new DelayedPacket();
p.Target = target;
p.Data = new byte[numBytes];
Buffer.BlockCopy(m_sendBuffer, 0, p.Data, 0, numBytes);
p.DelayedUntil = NetTime.Now + delay;
m_delayedPackets.Add(p);
}
// LogVerbose("Sending packet " + numBytes + " bytes - delayed " + NetTime.ToReadable(delay));
}
private void SendDelayedPackets()
{
if (m_delayedPackets.Count <= 0)
return;
double now = NetTime.Now;
bool connectionReset;
RestartDelaySending:
foreach (DelayedPacket p in m_delayedPackets)
{
if (now > p.DelayedUntil)
{
ActuallySendPacket(p.Data, p.Data.Length, p.Target, out connectionReset);
m_delayedPackets.Remove(p);
goto RestartDelaySending;
}
}
}
internal bool ActuallySendPacket(byte[] data, int numBytes, IPEndPoint target, out bool connectionReset)
{
connectionReset = false;
try
{
if (target.Address == IPAddress.Broadcast)
m_socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, true);
int bytesSent = m_socket.SendTo(data, 0, numBytes, SocketFlags.None, target);
if (numBytes != bytesSent)
LogWarning("Failed to send the full " + numBytes + "; only " + bytesSent + " bytes sent in packet!");
}
catch (SocketException sx)
{
if (sx.SocketErrorCode == SocketError.WouldBlock)
{
// send buffer full?
LogWarning("Socket threw exception; would block - send buffer full? Increase in NetPeerConfiguration");
return false;
}
if (sx.SocketErrorCode == SocketError.ConnectionReset)
{
// connection reset by peer, aka connection forcibly closed aka "ICMP port unreachable"
connectionReset = true;
return false;
}
LogError("Failed to send packet: " + sx);
}
catch (Exception ex)
{
LogError("Failed to send packet: " + ex);
}
finally
{
if (target.Address == IPAddress.Broadcast)
m_socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, false);
}
return true;
}
#else
//
// Release - just send the packet straight away
//
internal void SendPacket(int numBytes, IPEndPoint target, int numMessages, out bool connectionReset)
{
connectionReset = false;
try
{
if (target.Address == IPAddress.Broadcast)
m_socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, true);
int bytesSent = m_socket.SendTo(m_sendBuffer, 0, numBytes, SocketFlags.None, target);
if (numBytes != bytesSent)
LogWarning("Failed to send the full " + numBytes + "; only " + bytesSent + " bytes sent in packet!");
}
catch (SocketException sx)
{
if (sx.SocketErrorCode == SocketError.WouldBlock)
{
// send buffer full?
LogWarning("Socket threw exception; would block - send buffer full? Increase in NetPeerConfiguration");
return;
}
if (sx.SocketErrorCode == SocketError.ConnectionReset)
{
// connection reset by peer, aka connection forcibly closed aka "ICMP port unreachable"
connectionReset = true;
return;
}
LogError("Failed to send packet: " + sx);
}
catch (Exception ex)
{
LogError("Failed to send packet: " + ex);
}
finally
{
if (target.Address == IPAddress.Broadcast)
m_socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, false);
}
return;
}
private void SendCallBack(IAsyncResult res)
{
NetException.Assert(res.IsCompleted == true);
m_socket.EndSendTo(res);
}
#endif
}
}

View File

@@ -0,0 +1,51 @@
/* Copyright (c) 2010 Michael Lidgren
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.
*/
using System.Diagnostics;
namespace Lidgren.Network
{
public partial class NetPeer
{
[Conditional("DEBUG")]
internal void LogVerbose(string message)
{
if (m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.VerboseDebugMessage))
ReleaseMessage(CreateIncomingMessage(NetIncomingMessageType.VerboseDebugMessage, message));
}
[Conditional("DEBUG")]
internal void LogDebug(string message)
{
if (m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.DebugMessage))
ReleaseMessage(CreateIncomingMessage(NetIncomingMessageType.DebugMessage, message));
}
internal void LogWarning(string message)
{
if (m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.WarningMessage))
ReleaseMessage(CreateIncomingMessage(NetIncomingMessageType.WarningMessage, message));
}
internal void LogError(string message)
{
if (m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.ErrorMessage))
ReleaseMessage(CreateIncomingMessage(NetIncomingMessageType.ErrorMessage, message));
}
}
}

View File

@@ -0,0 +1,192 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Lidgren.Network
{
public partial class NetPeer
{
private List<byte[]> m_storagePool; // sorted smallest to largest
private NetQueue<NetOutgoingMessage> m_outgoingMessagesPool;
private NetQueue<NetIncomingMessage> m_incomingMessagesPool;
internal int m_storagePoolBytes;
private void InitializePools()
{
if (m_configuration.UseMessageRecycling)
{
m_storagePool = new List<byte[]>(16);
m_outgoingMessagesPool = new NetQueue<NetOutgoingMessage>(4);
m_incomingMessagesPool = new NetQueue<NetIncomingMessage>(4);
}
else
{
m_storagePool = null;
m_outgoingMessagesPool = null;
m_incomingMessagesPool = null;
}
}
internal byte[] GetStorage(int minimumCapacity)
{
if (m_storagePool == null)
return new byte[minimumCapacity];
lock (m_storagePool)
{
for (int i = 0; i < m_storagePool.Count; i++)
{
byte[] retval = m_storagePool[i];
if (retval != null && retval.Length >= minimumCapacity)
{
m_storagePool[i] = null;
m_storagePoolBytes -= retval.Length;
return retval;
}
}
}
m_statistics.m_bytesAllocated += minimumCapacity;
return new byte[minimumCapacity];
}
internal void Recycle(byte[] storage)
{
if (m_storagePool == null)
return;
int len = storage.Length;
lock (m_storagePool)
{
for (int i = 0; i < m_storagePool.Count; i++)
{
if (m_storagePool[i] == null)
{
m_storagePoolBytes += storage.Length;
m_storagePool[i] = storage;
return;
}
}
m_storagePoolBytes += storage.Length;
m_storagePool.Add(storage);
}
}
/// <summary>
/// Creates a new message for sending
/// </summary>
public NetOutgoingMessage CreateMessage()
{
return CreateMessage(m_configuration.m_defaultOutgoingMessageCapacity);
}
/// <summary>
/// Creates a new message for sending and writes the provided string to it
/// </summary>
public NetOutgoingMessage CreateMessage(string content)
{
byte[] bytes = Encoding.UTF8.GetBytes(content);
NetOutgoingMessage om = CreateMessage(2 + bytes.Length);
om.WriteVariableUInt32((uint)bytes.Length);
om.Write(bytes);
return om;
}
/// <summary>
/// Creates a new message for sending
/// </summary>
/// <param name="initialCapacity">initial capacity in bytes</param>
public NetOutgoingMessage CreateMessage(int initialCapacity)
{
NetOutgoingMessage retval;
if (m_outgoingMessagesPool == null || !m_outgoingMessagesPool.TryDequeue(out retval))
retval = new NetOutgoingMessage();
byte[] storage = GetStorage(initialCapacity);
retval.m_data = storage;
return retval;
}
internal NetIncomingMessage CreateIncomingMessage(NetIncomingMessageType tp, byte[] useStorageData)
{
NetIncomingMessage retval;
if (m_incomingMessagesPool == null || !m_incomingMessagesPool.TryDequeue(out retval))
retval = new NetIncomingMessage(tp);
else
retval.m_incomingMessageType = tp;
retval.m_data = useStorageData;
return retval;
}
internal NetIncomingMessage CreateIncomingMessage(NetIncomingMessageType tp, int minimumByteSize)
{
NetIncomingMessage retval;
if (m_incomingMessagesPool == null || !m_incomingMessagesPool.TryDequeue(out retval))
retval = new NetIncomingMessage(tp);
else
retval.m_incomingMessageType = tp;
retval.m_data = GetStorage(minimumByteSize);
return retval;
}
/// <summary>
/// Recycles a NetIncomingMessage instance for reuse; taking pressure off the garbage collector
/// </summary>
public void Recycle(NetIncomingMessage msg)
{
if (m_incomingMessagesPool == null)
return;
#if DEBUG
if (m_incomingMessagesPool.Contains(msg))
throw new NetException("Recyling already recycled message! Thread race?");
#endif
byte[] storage = msg.m_data;
msg.m_data = null;
Recycle(storage);
msg.Reset();
m_incomingMessagesPool.Enqueue(msg);
}
internal void Recycle(NetOutgoingMessage msg)
{
if (m_outgoingMessagesPool == null)
return;
#if DEBUG
if (m_outgoingMessagesPool.Contains(msg))
throw new NetException("Recyling already recycled message! Thread race?");
#endif
byte[] storage = msg.m_data;
msg.m_data = null;
// message fragments cannot be recycled
// TODO: find a way to recycle large message after all fragments has been acknowledged; or? possibly better just to garbage collect them
if (msg.m_fragmentGroup == 0)
Recycle(storage);
msg.Reset();
m_outgoingMessagesPool.Enqueue(msg);
}
/// <summary>
/// Creates an incoming message with the required capacity for releasing to the application
/// </summary>
internal NetIncomingMessage CreateIncomingMessage(NetIncomingMessageType tp, string text)
{
NetIncomingMessage retval;
if (string.IsNullOrEmpty(text))
{
retval = CreateIncomingMessage(tp, 1);
retval.Write("");
return retval;
}
byte[] bytes = System.Text.Encoding.UTF8.GetBytes(text);
retval = CreateIncomingMessage(tp, bytes.Length + (bytes.Length > 127 ? 2 : 1));
retval.Write(text);
return retval;
}
}
}

View File

@@ -0,0 +1,282 @@
/* Copyright (c) 2010 Michael Lidgren
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.
*/
using System.Collections.Generic;
using System;
namespace Lidgren.Network
{
public partial class NetPeer
{
internal int m_storedBytes;
private int m_maxStoredBytes;
private readonly List<byte[]> m_storagePool = new List<byte[]>();
private readonly NetQueue<NetIncomingMessage> m_incomingMessagesPool = new NetQueue<NetIncomingMessage>(16);
private readonly NetQueue<NetOutgoingMessage> m_outgoingMessagesPool = new NetQueue<NetOutgoingMessage>(16);
private void InitializeRecycling()
{
m_storagePool.Clear();
m_storedBytes = 0;
m_maxStoredBytes = m_configuration.m_maxRecycledBytesKept;
m_incomingMessagesPool.Clear();
m_outgoingMessagesPool.Clear();
}
internal byte[] GetStorage(int requiredBytes)
{
if (m_storagePool.Count < 1)
{
m_statistics.m_bytesAllocated += requiredBytes;
return new byte[requiredBytes];
}
lock (m_storagePool)
{
// search from end to start
for (int i = m_storagePool.Count - 1; i >= 0; i--)
{
byte[] retval = m_storagePool[i];
if (retval.Length >= requiredBytes)
{
m_storagePool.RemoveAt(i);
m_storedBytes -= retval.Length;
return retval;
}
}
}
m_statistics.m_bytesAllocated += requiredBytes;
return new byte[requiredBytes];
}
/// <summary>
/// Creates a new message for sending
/// </summary>
public NetOutgoingMessage CreateMessage()
{
return CreateMessage(m_configuration.DefaultOutgoingMessageCapacity);
}
/// <summary>
/// Creates a new message for sending
/// </summary>
/// <param name="initialCapacity">initial capacity in bytes</param>
public NetOutgoingMessage CreateMessage(int initialCapacity)
{
NetOutgoingMessage retval;
if (m_outgoingMessagesPool.TryDequeue(out retval))
retval.Reset();
else
retval = new NetOutgoingMessage();
byte[] storage = GetStorage(initialCapacity);
retval.m_data = storage;
return retval;
}
internal NetOutgoingMessage CreateLibraryMessage(NetMessageLibraryType tp, string content)
{
NetOutgoingMessage retval = CreateMessage(1 + (content == null ? 0 : content.Length));
retval.m_libType = tp;
retval.Write((content == null ? "" : content));
return retval;
}
/// <summary>
/// Recycle the message to the library for reuse
/// </summary>
public void Recycle(NetIncomingMessage msg)
{
if (msg == null)
throw new ArgumentNullException("msg");
if (msg.m_status != NetIncomingMessageReleaseStatus.ReleasedToApplication)
throw new NetException("Message not under application control; recycled more than once?");
msg.m_status = NetIncomingMessageReleaseStatus.RecycledByApplication;
if (msg.m_data != null)
{
lock (m_storagePool)
{
#if DEBUG
if (m_storagePool.Contains(msg.m_data))
throw new NetException("Storage pool object recycled twice!");
#endif
m_storedBytes += msg.m_data.Length;
m_storagePool.Add(msg.m_data);
}
msg.m_data = null;
}
m_incomingMessagesPool.Enqueue(msg);
}
/// <summary>
/// Recycle the message to the library for reuse
/// </summary>
internal void Recycle(NetOutgoingMessage msg)
{
VerifyNetworkThread();
#if DEBUG
lock (m_connections)
{
foreach (NetConnection conn in m_connections)
{
for (int i = 0; i < conn.m_unsentMessages.Count; i++)
{
NetSending send = conn.m_unsentMessages.TryPeek(i);
if (send != null && send.Message == msg)
throw new NetException("Ouch! Recycling unsent message!");
foreach (NetSending asend in conn.m_unackedSends)
if (asend.Message == msg)
throw new NetException("Ouch! Recycling stored message!");
}
}
}
#endif
NetException.Assert(msg.m_numUnfinishedSendings == 0, "Recycling m_numUnfinishedSendings is " + msg.m_numUnfinishedSendings + " (expected 0)");
if (msg.m_data != null)
{
lock (m_storagePool)
{
if (!m_storagePool.Contains(msg.m_data))
{
m_storedBytes += msg.m_data.Length;
m_storagePool.Add(msg.m_data);
}
}
msg.m_data = null;
}
m_outgoingMessagesPool.Enqueue(msg);
}
/// <summary>
/// Call to check if storage pool should be reduced
/// </summary>
private void ReduceStoragePool()
{
VerifyNetworkThread();
if (m_storedBytes < m_configuration.m_maxRecycledBytesKept)
return; // never mind threading, no big deal if storage is larger than config setting for a frame
int wasStoredBytes;
int reduceTo;
lock (m_storagePool)
{
// since newly stored message at added to the end; remove from the start
wasStoredBytes = m_storedBytes;
reduceTo = m_maxStoredBytes / 2;
int remove = 0;
while (m_storedBytes > reduceTo && remove < m_storagePool.Count)
{
byte[] arr = m_storagePool[0];
m_storedBytes -= arr.Length;
remove++;
}
if (remove > 0)
m_storagePool.RemoveRange(0, remove);
}
// done
LogDebug("Reduced recycled bytes pool from " + wasStoredBytes + " bytes to " + m_storedBytes + " bytes (target " + reduceTo + ")");
return;
}
/// <summary>
/// Creates an incoming message with the required capacity for releasing to the application
/// </summary>
internal NetIncomingMessage CreateIncomingMessage(NetIncomingMessageType tp, string contents)
{
NetIncomingMessage retval;
if (string.IsNullOrEmpty(contents))
{
retval = CreateIncomingMessage(tp, 1);
retval.Write("");
return retval;
}
byte[] bytes = System.Text.Encoding.UTF8.GetBytes(contents);
retval = CreateIncomingMessage(tp, bytes.Length + (bytes.Length > 127 ? 2 : 1));
retval.Write(contents);
return retval;
}
/// <summary>
/// Creates an incoming message with the required capacity for releasing to the application
/// </summary>
internal NetIncomingMessage CreateIncomingMessage(NetIncomingMessageType tp, int requiredCapacity)
{
NetIncomingMessage retval;
if (m_incomingMessagesPool.TryDequeue(out retval))
retval.Reset();
else
retval = new NetIncomingMessage();
NetException.Assert(retval.m_status == NetIncomingMessageReleaseStatus.NotReleased);
retval.m_incomingType = tp;
retval.m_senderConnection = null;
retval.m_senderEndpoint = null;
retval.m_status = NetIncomingMessageReleaseStatus.NotReleased;
if (requiredCapacity > 0)
{
byte[] storage = GetStorage(requiredCapacity);
retval.m_data = storage;
}
else
{
retval.m_data = null;
}
return retval;
}
internal NetIncomingMessage CreateIncomingMessage(NetIncomingMessageType tp, byte[] copyFrom, int offset, int copyLength)
{
NetIncomingMessage retval;
if (m_incomingMessagesPool.TryDequeue(out retval))
retval.Reset();
else
retval = new NetIncomingMessage();
NetException.Assert(retval.m_status == NetIncomingMessageReleaseStatus.NotReleased);
retval.m_data = GetStorage(copyLength);
Buffer.BlockCopy(copyFrom, offset, retval.m_data, 0, copyLength);
retval.m_bitLength = copyLength * 8;
retval.m_incomingType = tp;
retval.m_senderConnection = null;
retval.m_senderEndpoint = null;
return retval;
}
}
}

View File

@@ -0,0 +1,161 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Net;
namespace Lidgren.Network
{
public partial class NetPeer
{
/// <summary>
/// Send a message to a specific connection
/// </summary>
/// <param name="msg">The message to send</param>
/// <param name="recipient">The recipient connection</param>
/// <param name="method">How to deliver the message</param>
public NetSendResult SendMessage(NetOutgoingMessage msg, NetConnection recipient, NetDeliveryMethod method)
{
return SendMessage(msg, recipient, method, 0);
}
/// <summary>
/// Send a message to a specific connection
/// </summary>
/// <param name="msg">The message to send</param>
/// <param name="recipient">The recipient connection</param>
/// <param name="method">How to deliver the message</param>
/// <param name="sequenceChannel">Sequence channel within the delivery method</param>
public NetSendResult SendMessage(NetOutgoingMessage msg, NetConnection recipient, NetDeliveryMethod method, int sequenceChannel)
{
if (msg == null)
throw new ArgumentNullException("msg");
if (recipient == null)
throw new ArgumentNullException("recipient");
NetException.Assert(
((method != NetDeliveryMethod.Unreliable && method != NetDeliveryMethod.ReliableUnordered) ||
((method == NetDeliveryMethod.Unreliable || method == NetDeliveryMethod.ReliableUnordered) && sequenceChannel == 0)),
"Delivery method " + method + " cannot use sequence channels other than 0!"
);
NetException.Assert(method != NetDeliveryMethod.Unknown, "Bad delivery method!");
if (msg.m_isSent)
throw new NetException("This message has already been sent! Use NetPeer.SendMessage() to send to multiple recipients efficiently");
int len = msg.LengthBytes;
if (len <= m_configuration.MaximumTransmissionUnit)
{
Interlocked.Increment(ref msg.m_recyclingCount);
return recipient.EnqueueMessage(msg, method, sequenceChannel);
}
else
{
// message must be fragmented!
SendFragmentedMessage(msg, new NetConnection[] { recipient }, method, sequenceChannel);
return NetSendResult.Queued; // could be different for each connection; Queued is "most true"
}
}
public void SendMessage(NetOutgoingMessage msg, IList<NetConnection> recipients, NetDeliveryMethod method, int sequenceChannel)
{
if (msg == null)
throw new ArgumentNullException("msg");
if (recipients == null)
throw new ArgumentNullException("recipients");
if (method == NetDeliveryMethod.Unreliable || method == NetDeliveryMethod.ReliableUnordered)
NetException.Assert(sequenceChannel == 0, "Delivery method " + method + " cannot use sequence channels other than 0!");
if (msg.m_isSent)
throw new NetException("This message has already been sent! Use NetPeer.SendMessage() to send to multiple recipients efficiently");
int len = msg.LengthBytes;
if (len <= m_configuration.MaximumTransmissionUnit)
{
Interlocked.Add(ref msg.m_recyclingCount, recipients.Count);
foreach (NetConnection conn in recipients)
{
if (conn == null)
{
Interlocked.Decrement(ref msg.m_recyclingCount);
continue;
}
NetSendResult res = conn.EnqueueMessage(msg, method, sequenceChannel);
if (res == NetSendResult.Dropped)
Interlocked.Decrement(ref msg.m_recyclingCount);
}
}
else
{
// message must be fragmented!
SendFragmentedMessage(msg, recipients, method, sequenceChannel);
}
return;
}
/// <summary>
/// Send a message to an unconnected host
/// </summary>
public void SendUnconnectedMessage(NetOutgoingMessage msg, string host, int port)
{
if (msg == null)
throw new ArgumentNullException("msg");
if (host == null)
throw new ArgumentNullException("host");
if (msg.m_isSent)
throw new NetException("This message has already been sent! Use NetPeer.SendMessage() to send to multiple recipients efficiently");
if (msg.LengthBytes > m_configuration.MaximumTransmissionUnit)
throw new NetException("Unconnected messages too long! Must be shorter than NetConfiguration.MaximumTransmissionUnit (currently " + m_configuration.MaximumTransmissionUnit + ")");
IPAddress adr = NetUtility.Resolve(host);
if (adr == null)
throw new NetException("Failed to resolve " + host);
msg.m_messageType = NetMessageType.Unconnected;
Interlocked.Increment(ref msg.m_recyclingCount);
m_unsentUnconnectedMessages.Enqueue(new NetTuple<IPEndPoint, NetOutgoingMessage>(new IPEndPoint(adr, port), msg));
}
/// <summary>
/// Send a message to an unconnected host
/// </summary>
public void SendUnconnectedMessage(NetOutgoingMessage msg, IPEndPoint recipient)
{
if (msg == null)
throw new ArgumentNullException("msg");
if (recipient == null)
throw new ArgumentNullException("recipient");
if (msg.m_isSent)
throw new NetException("This message has already been sent! Use NetPeer.SendMessage() to send to multiple recipients efficiently");
if (msg.LengthBytes > m_configuration.MaximumTransmissionUnit)
throw new NetException("Unconnected messages too long! Must be shorter than NetConfiguration.MaximumTransmissionUnit (currently " + m_configuration.MaximumTransmissionUnit + ")");
msg.m_messageType = NetMessageType.Unconnected;
Interlocked.Increment(ref msg.m_recyclingCount);
m_unsentUnconnectedMessages.Enqueue(new NetTuple<IPEndPoint, NetOutgoingMessage>(recipient, msg));
}
/// <summary>
/// Send a message to an unconnected host
/// </summary>
public void SendUnconnectedMessage(NetOutgoingMessage msg, IList<IPEndPoint> recipients)
{
if (msg == null)
throw new ArgumentNullException("msg");
if (recipients == null)
throw new ArgumentNullException("recipients");
if (msg.m_isSent)
throw new NetException("This message has already been sent! Use NetPeer.SendMessage() to send to multiple recipients efficiently");
if (msg.LengthBytes > m_configuration.MaximumTransmissionUnit)
throw new NetException("Unconnected messages too long! Must be shorter than NetConfiguration.MaximumTransmissionUnit (currently " + m_configuration.MaximumTransmissionUnit + ")");
msg.m_messageType = NetMessageType.Unconnected;
Interlocked.Add(ref msg.m_recyclingCount, recipients.Count);
foreach(IPEndPoint ep in recipients)
m_unsentUnconnectedMessages.Enqueue(new NetTuple<IPEndPoint, NetOutgoingMessage>(ep, msg));
}
}
}

278
Lidgren.Network/NetPeer.cs Normal file
View File

@@ -0,0 +1,278 @@
using System;
using System.Threading;
using System.Collections.Generic;
using System.Net;
namespace Lidgren.Network
{
/// <summary>
/// Represents a local peer capable of holding zero, one or more connections to remote peers
/// </summary>
public partial class NetPeer
{
private static int s_initializedPeersCount;
private int m_listenPort;
private object m_tag;
internal readonly List<NetConnection> m_connections;
private readonly Dictionary<IPEndPoint, NetConnection> m_connectionLookup;
private string m_shutdownReason;
/// <summary>
/// Gets the NetPeerStatus of the NetPeer
/// </summary>
public NetPeerStatus Status { get { return m_status; } }
/// <summary>
/// Signalling event which can be waited on to determine when a message is queued for reading.
/// Note that there is no guarantee that after the event is signaled the blocked thread will
/// find the message in the queue. Other user created threads could be preempted and dequeue
/// the message before the waiting thread wakes up.
/// </summary>
public AutoResetEvent MessageReceivedEvent { get { return m_messageReceivedEvent; } }
/// <summary>
/// Gets a unique identifier for this NetPeer based on Mac address and ip/port. Note! Not available until Start() has been called!
/// </summary>
public long UniqueIdentifier { get { return m_uniqueIdentifier; } }
/// <summary>
/// Gets the port number this NetPeer is listening and sending on, if Start() has been called
/// </summary>
public int Port { get { return m_listenPort; } }
/// <summary>
/// Gets or sets the application defined object containing data about the peer
/// </summary>
public object Tag
{
get { return m_tag; }
set { m_tag = value; }
}
/// <summary>
/// Gets a copy of the list of connections
/// </summary>
public List<NetConnection> Connections
{
get
{
lock (m_connections)
return new List<NetConnection>(m_connections);
}
}
/// <summary>
/// Gets the number of active connections
/// </summary>
public int ConnectionsCount
{
get { return m_connections.Count; }
}
/// <summary>
/// Statistics on this NetPeer since it was initialized
/// </summary>
public NetPeerStatistics Statistics
{
get { return m_statistics; }
}
/// <summary>
/// Gets the configuration used to instanciate this NetPeer
/// </summary>
public NetPeerConfiguration Configuration { get { return m_configuration; } }
public NetPeer(NetPeerConfiguration config)
{
m_configuration = config;
m_statistics = new NetPeerStatistics(this);
m_releasedIncomingMessages = new NetQueue<NetIncomingMessage>(4);
m_unsentUnconnectedMessages = new NetQueue<NetTuple<IPEndPoint, NetOutgoingMessage>>(2);
m_connections = new List<NetConnection>();
m_connectionLookup = new Dictionary<IPEndPoint, NetConnection>();
m_handshakes = new Dictionary<IPEndPoint, NetConnection>();
m_senderRemote = (EndPoint)new IPEndPoint(IPAddress.Any, 0);
m_status = NetPeerStatus.NotRunning;
m_receivedFragmentGroups = new Dictionary<int, ReceivedFragmentGroup>();
}
/// <summary>
/// Binds to socket and spawns the networking thread
/// </summary>
public void Start()
{
if (m_status != NetPeerStatus.NotRunning)
{
// already running! Just ignore...
LogWarning("Start() called on already running NetPeer - ignoring.");
return;
}
m_status = NetPeerStatus.Starting;
// fix network thread name
if (m_configuration.NetworkThreadName == "Lidgren network thread")
{
int pc = Interlocked.Increment(ref s_initializedPeersCount);
m_configuration.NetworkThreadName = "Lidgren network thread " + pc.ToString();
}
InitializeNetwork();
// start network thread
m_networkThread = new Thread(new ThreadStart(NetworkLoop));
m_networkThread.Name = m_configuration.NetworkThreadName;
m_networkThread.IsBackground = true;
m_networkThread.Start();
// allow some time for network thread to start up in case they call Connect() immediately
Thread.Sleep(10);
}
internal NetConnection GetConnection(IPEndPoint ep)
{
NetConnection retval;
m_connectionLookup.TryGetValue(ep, out retval);
return retval;
}
/// <summary>
/// Read a pending message from any connection, blocking up to maxMillis if needed
/// </summary>
public NetIncomingMessage WaitMessage(int maxMillis)
{
if (m_messageReceivedEvent != null)
m_messageReceivedEvent.WaitOne(maxMillis);
return ReadMessage();
}
/// <summary>
/// Read a pending message from any connection, if any
/// </summary>
public NetIncomingMessage ReadMessage()
{
NetIncomingMessage retval;
if (m_releasedIncomingMessages.TryDequeue(out retval))
{
if (retval.MessageType == NetIncomingMessageType.StatusChanged)
{
NetConnectionStatus status = (NetConnectionStatus)retval.PeekByte();
retval.SenderConnection.m_visibleStatus = status;
}
}
return retval;
}
// send message immediately
internal void SendLibrary(NetOutgoingMessage msg, IPEndPoint recipient)
{
VerifyNetworkThread();
NetException.Assert(msg.m_isSent == false);
bool connReset;
int len = msg.Encode(m_sendBuffer, 0, 0);
SendPacket(len, recipient, 1, out connReset);
}
/// <summary>
/// Create a connection to a remote endpoint
/// </summary>
public NetConnection Connect(string host, int port)
{
return Connect(new IPEndPoint(NetUtility.Resolve(host), port), null);
}
/// <summary>
/// Create a connection to a remote endpoint
/// </summary>
public NetConnection Connect(string host, int port, NetOutgoingMessage hailMessage)
{
return Connect(new IPEndPoint(NetUtility.Resolve(host), port), hailMessage);
}
/// <summary>
/// Create a connection to a remote endpoint
/// </summary>
public NetConnection Connect(IPEndPoint remoteEndpoint)
{
return Connect(remoteEndpoint, null);
}
/// <summary>
/// Create a connection to a remote endpoint
/// </summary>
public virtual NetConnection Connect(IPEndPoint remoteEndpoint, NetOutgoingMessage hailMessage)
{
if (remoteEndpoint == null)
throw new ArgumentNullException("remoteEndpoint");
lock (m_connections)
{
if (m_status == NetPeerStatus.NotRunning)
throw new NetException("Must call Start() first");
if (m_connectionLookup.ContainsKey(remoteEndpoint))
throw new NetException("Already connected to that endpoint!");
NetConnection hs;
if (m_handshakes.TryGetValue(remoteEndpoint, out hs))
{
// already trying to connect to that endpoint; make another try
switch (hs.Status)
{
case NetConnectionStatus.InitiatedConnect:
// send another connect
hs.m_connectRequested = true;
break;
case NetConnectionStatus.RespondedConnect:
// send another response
hs.SendConnectResponse(false);
break;
default:
// weird
LogWarning("Weird situation; Connect() already in progress to remote endpoint; but hs status is " + hs.Status);
break;
}
}
NetConnection conn = new NetConnection(this, remoteEndpoint);
conn.m_localHailMessage = hailMessage;
// handle on network thread
conn.m_connectRequested = true;
conn.m_connectionInitiator = true;
m_handshakes.Add(remoteEndpoint, conn);
return conn;
}
}
#if DEBUG
public void RawSend(byte[] arr, int offset, int length, IPEndPoint destination)
{
// wrong thread - this miiiight crash with network thread... but what's a boy to do.
Array.Copy(arr, offset, m_sendBuffer, 0, length);
bool unused;
SendPacket(length, destination, 1, out unused);
}
#endif
/// <summary>
/// Disconnects all active connections and closes the socket
/// </summary>
public void Shutdown(string bye)
{
// called on user thread
if (m_socket == null)
return; // already shut down
LogDebug("Shutdown requested");
m_shutdownReason = bye;
m_status = NetPeerStatus.ShutdownRequested;
}
}
}

View File

@@ -0,0 +1,352 @@
/* Copyright (c) 2010 Michael Lidgren
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.
*/
using System;
using System.Net;
namespace Lidgren.Network
{
/// <summary>
/// Partly immutable after NetPeer has been initialized
/// </summary>
public sealed class NetPeerConfiguration
{
private const string c_isLockedMessage = "You may not modify the NetPeerConfiguration after it has been used to initialize a NetPeer";
private bool m_isLocked;
private readonly string m_appIdentifier;
private string m_networkThreadName;
private IPAddress m_localAddress;
internal bool m_acceptIncomingConnections;
internal int m_maximumConnections;
internal int m_maximumTransmissionUnit;
internal int m_defaultOutgoingMessageCapacity;
internal float m_pingInterval;
internal bool m_useMessageRecycling;
internal float m_connectionTimeout;
internal NetIncomingMessageType m_disabledTypes;
internal int m_port;
internal int m_receiveBufferSize;
internal int m_sendBufferSize;
// bad network simulation
internal float m_loss;
internal float m_duplicates;
internal float m_minimumOneWayLatency;
internal float m_randomOneWayLatency;
public NetPeerConfiguration(string appIdentifier)
{
if (string.IsNullOrEmpty(appIdentifier))
throw new NetException("App identifier must be at least one character long");
m_appIdentifier = appIdentifier.ToString(System.Globalization.CultureInfo.InvariantCulture);
//
// default values
//
m_disabledTypes = NetIncomingMessageType.ConnectionApproval | NetIncomingMessageType.UnconnectedData | NetIncomingMessageType.VerboseDebugMessage;
m_networkThreadName = "Lidgren network thread";
m_localAddress = IPAddress.Any;
m_port = 0;
m_receiveBufferSize = 131071;
m_sendBufferSize = 131071;
m_acceptIncomingConnections = false;
m_maximumConnections = 32;
m_defaultOutgoingMessageCapacity = 16;
m_pingInterval = 4.0f;
m_connectionTimeout = 25.0f;
m_useMessageRecycling = true;
// Maximum transmission unit
// Ethernet can take 1500 bytes of payload, so lets stay below that.
// The aim is for a max full packet to be 1440 bytes (30 x 48 bytes, lower than 1468)
// -20 bytes IP header
// -8 bytes UDP header
// -4 bytes to be on the safe side and align to 8-byte boundary
// Total 1408 bytes
// Note that lidgren headers (5 bytes) are not included here; since it's part of the "mtu payload"
m_maximumTransmissionUnit = 1408;
m_loss = 0.0f;
m_minimumOneWayLatency = 0.0f;
m_randomOneWayLatency = 0.0f;
m_duplicates = 0.0f;
m_isLocked = false;
}
internal void Lock()
{
m_isLocked = true;
}
/// <summary>
/// Gets the identifier of this application; the library can only connect to matching app identifier peers
/// </summary>
public string AppIdentifier
{
get { return m_appIdentifier; }
}
/// <summary>
/// Enables receiving of the specified type of message
/// </summary>
public void EnableMessageType(NetIncomingMessageType type)
{
m_disabledTypes &= (~type);
}
/// <summary>
/// Disables receiving of the specified type of message
/// </summary>
public void DisableMessageType(NetIncomingMessageType type)
{
m_disabledTypes |= type;
}
/// <summary>
/// Enables or disables receiving of the specified type of message
/// </summary>
public void SetMessageTypeEnabled(NetIncomingMessageType type, bool enabled)
{
if (enabled)
m_disabledTypes &= (~type);
else
m_disabledTypes |= type;
}
/// <summary>
/// Gets if receiving of the specified type of message is enabled
/// </summary>
public bool IsMessageTypeEnabled(NetIncomingMessageType type)
{
return !((m_disabledTypes & type) == type);
}
/// <summary>
/// Gets or sets the name of the library network thread. Cannot be changed once NetPeer is initialized.
/// </summary>
public string NetworkThreadName
{
get { return m_networkThreadName; }
set
{
if (m_isLocked)
throw new NetException("NetworkThreadName may not be set after the NetPeer which uses the configuration has been started");
m_networkThreadName = value;
}
}
/// <summary>
/// Gets or sets the maximum amount of connections this peer can hold. Cannot be changed once NetPeer is initialized.
/// </summary>
public int MaximumConnections
{
get { return m_maximumConnections; }
set
{
if (m_isLocked)
throw new NetException(c_isLockedMessage);
m_maximumConnections = value;
}
}
/// <summary>
/// Gets or sets the maximum amount of bytes to send in a single packet, excluding ip, udp and lidgren headers
/// </summary>
public int MaximumTransmissionUnit
{
get { return m_maximumTransmissionUnit; }
set
{
if (value < 1 || value >= ((ushort.MaxValue + 1) / 8))
throw new NetException("MaximumTransmissionUnit must be between 1 and " + (((ushort.MaxValue + 1) / 8) - 1) + " bytes");
m_maximumTransmissionUnit = value;
}
}
/// <summary>
/// Gets or sets the default capacity in bytes when NetPeer.CreateMessage() is called without argument
/// </summary>
public int DefaultOutgoingMessageCapacity
{
get { return m_defaultOutgoingMessageCapacity; }
set { m_defaultOutgoingMessageCapacity = value; }
}
/// <summary>
/// Gets or sets the time between latency calculating pings
/// </summary>
public float PingInterval
{
get { return m_pingInterval; }
set { m_pingInterval = value; }
}
/// <summary>
/// Gets or sets if the library should recycling messages to avoid excessive garbage collection. Cannot be changed once NetPeer is initialized.
/// </summary>
public bool UseMessageRecycling
{
get { return m_useMessageRecycling; }
set
{
if (m_isLocked)
throw new NetException(c_isLockedMessage);
m_useMessageRecycling = value;
}
}
/// <summary>
/// Gets or sets the number of seconds timeout will be postponed on a successful ping/pong
/// </summary>
public float ConnectionTimeout
{
get { return m_connectionTimeout; }
set
{
if (value < m_pingInterval)
throw new NetException("Connection timeout cannot be lower than ping interval!");
m_connectionTimeout = value;
}
}
/// <summary>
/// Gets or sets the local ip address to bind to. Defaults to IPAddress.Any. Cannot be changed once NetPeer is initialized.
/// </summary>
public IPAddress LocalAddress
{
get { return m_localAddress; }
set
{
if (m_isLocked)
throw new NetException(c_isLockedMessage);
m_localAddress = value;
}
}
/// <summary>
/// Gets or sets the local port to bind to. Defaults to 0. Cannot be changed once NetPeer is initialized.
/// </summary>
public int Port
{
get { return m_port; }
set
{
if (m_isLocked)
throw new NetException(c_isLockedMessage);
m_port = value;
}
}
/// <summary>
/// Gets or sets the size in bytes of the receiving buffer. Defaults to 131071 bytes. Cannot be changed once NetPeer is initialized.
/// </summary>
public int ReceiveBufferSize
{
get { return m_receiveBufferSize; }
set
{
if (m_isLocked)
throw new NetException(c_isLockedMessage);
m_receiveBufferSize = value;
}
}
/// <summary>
/// Gets or sets the size in bytes of the sending buffer. Defaults to 131071 bytes. Cannot be changed once NetPeer is initialized.
/// </summary>
public int SendBufferSize
{
get { return m_sendBufferSize; }
set
{
if (m_isLocked)
throw new NetException(c_isLockedMessage);
m_sendBufferSize = value;
}
}
/// <summary>
/// Gets or sets if the NetPeer should accept incoming connections. This is automatically set to true in NetServer and false in NetClient.
/// </summary>
public bool AcceptIncomingConnections
{
get { return m_acceptIncomingConnections; }
set { m_acceptIncomingConnections = value; }
}
#if DEBUG
/// <summary>
/// Gets or sets the simulated amount of sent packets lost from 0.0f to 1.0f
/// </summary>
public float SimulatedLoss
{
get { return m_loss; }
set { m_loss = value; }
}
/// <summary>
/// Gets or sets the minimum simulated amount of one way latency for sent packets in seconds
/// </summary>
public float SimulatedMinimumLatency
{
get { return m_minimumOneWayLatency; }
set { m_minimumOneWayLatency = value; }
}
/// <summary>
/// Gets or sets the simulated added random amount of one way latency for sent packets in seconds
/// </summary>
public float SimulatedRandomLatency
{
get { return m_randomOneWayLatency; }
set { m_randomOneWayLatency = value; }
}
/// <summary>
/// Gets the average simulated one way latency in seconds
/// </summary>
public float SimulatedAverageLatency
{
get { return m_minimumOneWayLatency + (m_randomOneWayLatency * 0.5f); }
}
/// <summary>
/// Gets or sets the simulated amount of duplicated packets from 0.0f to 1.0f
/// </summary>
public float SimulatedDuplicatesChance
{
get { return m_duplicates; }
set { m_duplicates = value; }
}
#endif
/// <summary>
/// Creates a memberwise shallow clone of this configuration
/// </summary>
public NetPeerConfiguration Clone()
{
NetPeerConfiguration retval = this.MemberwiseClone() as NetPeerConfiguration;
retval.m_isLocked = false;
return retval;
}
}
}

View File

@@ -0,0 +1,134 @@
/* Copyright (c) 2010 Michael Lidgren
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.
*/
using System;
using System.Text;
using System.Diagnostics;
namespace Lidgren.Network
{
/// <summary>
/// Statistics for a NetPeer instance
/// </summary>
public sealed class NetPeerStatistics
{
private readonly NetPeer m_peer;
internal int m_sentPackets;
internal int m_receivedPackets;
internal int m_sentMessages;
internal int m_receivedMessages;
internal int m_sentBytes;
internal int m_receivedBytes;
internal long m_bytesAllocated;
internal NetPeerStatistics(NetPeer peer)
{
m_peer = peer;
Reset();
}
internal void Reset()
{
m_sentPackets = 0;
m_receivedPackets = 0;
m_sentMessages = 0;
m_receivedMessages = 0;
m_sentBytes = 0;
m_receivedBytes = 0;
m_bytesAllocated = 0;
}
/// <summary>
/// Gets the number of sent packets since the NetPeer was initialized
/// </summary>
public int SentPackets { get { return m_sentPackets; } }
/// <summary>
/// Gets the number of received packets since the NetPeer was initialized
/// </summary>
public int ReceivedPackets { get { return m_receivedPackets; } }
/// <summary>
/// Gets the number of sent messages since the NetPeer was initialized
/// </summary>
public int SentMessages { get { return m_sentMessages; } }
/// <summary>
/// Gets the number of received messages since the NetPeer was initialized
/// </summary>
public int ReceivedMessages { get { return m_receivedMessages; } }
/// <summary>
/// Gets the number of sent bytes since the NetPeer was initialized
/// </summary>
public int SentBytes { get { return m_sentBytes; } }
/// <summary>
/// Gets the number of received bytes since the NetPeer was initialized
/// </summary>
public int ReceivedBytes { get { return m_receivedBytes; } }
/// <summary>
/// Gets the number of bytes allocated (and possibly garbage collected) for message storage
/// </summary>
public long StorageBytesAllocated { get { return m_bytesAllocated; } }
/// <summary>
/// Gets the number of bytes in the recycled pool
/// </summary>
public int BytesInRecyclePool { get { return m_peer.m_storagePoolBytes; } }
[Conditional("DEBUG")]
internal void PacketSent(int numBytes, int numMessages)
{
m_sentPackets++;
m_sentBytes += numBytes;
m_sentMessages += numMessages;
}
[Conditional("DEBUG")]
internal void PacketReceived(int numBytes, int numMessages)
{
m_receivedPackets++;
m_receivedBytes += numBytes;
m_receivedMessages += numMessages;
}
/// <summary>
/// Returns a string that represents this object
/// </summary>
public override string ToString()
{
StringBuilder bdr = new StringBuilder();
bdr.AppendLine(m_peer.ConnectionsCount.ToString() + " connections");
bdr.AppendLine("Sent " + m_sentBytes + " bytes in " + m_sentMessages + " messages in " + m_sentPackets + " packets");
bdr.AppendLine("Received " + m_receivedBytes + " bytes in " + m_receivedMessages + " messages in " + m_receivedPackets + " packets");
bdr.AppendLine("Storage allocated " + m_bytesAllocated + " bytes");
bdr.AppendLine("Recycled pool " + m_peer.m_storagePoolBytes + " bytes");
return bdr.ToString();
}
}
}

View File

@@ -0,0 +1,49 @@
/* Copyright (c) 2010 Michael Lidgren
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.
*/
using System;
namespace Lidgren.Network
{
/// <summary>
/// Status for a NetPeer instance
/// </summary>
public enum NetPeerStatus
{
/// <summary>
/// NetPeer is not running; socket is not bound
/// </summary>
NotRunning = 0,
/// <summary>
/// NetPeer is in the process of starting up
/// </summary>
Starting = 1,
/// <summary>
/// NetPeer is bound to socket and listening for packets
/// </summary>
Running = 2,
/// <summary>
/// Shutdown has been requested and will be executed shortly
/// </summary>
ShutdownRequested = 3,
}
}

231
Lidgren.Network/NetQueue.cs Normal file
View File

@@ -0,0 +1,231 @@
/* Copyright (c) 2010 Michael Lidgren
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.
*/
using System;
using System.Diagnostics;
namespace Lidgren.Network
{
/// <summary>
/// Thread safe (blocking) expanding queue with TryDequeue() and EnqueueFirst()
/// </summary>
[DebuggerDisplay("Count={Count} Capacity={Capacity}")]
public sealed class NetQueue<T>
{
// Example:
// m_capacity = 8
// m_size = 6
// m_head = 4
//
// [0] item
// [1] item (tail = ((head + size - 1) % capacity)
// [2]
// [3]
// [4] item (head)
// [5] item
// [6] item
// [7] item
//
private T[] m_items;
private readonly object m_lock;
private int m_size;
private int m_head;
/// <summary>
/// Gets the number of items in the queue
/// </summary>
public int Count { get { return m_size; } }
/// <summary>
/// Gets the current capacity for the queue
/// </summary>
public int Capacity { get { return m_items.Length; } }
public NetQueue(int initialCapacity)
{
m_lock = new object();
m_items = new T[initialCapacity];
}
/// <summary>
/// Adds an item last/tail of the queue
/// </summary>
public void Enqueue(T item)
{
lock (m_lock)
{
if (m_size == m_items.Length)
SetCapacity(m_items.Length + 8);
int slot = (m_head + m_size) % m_items.Length;
m_items[slot] = item;
m_size++;
}
}
/// <summary>
/// Places an item first, at the head of the queue
/// </summary>
public void EnqueueFirst(T item)
{
lock (m_lock)
{
if (m_size >= m_items.Length)
SetCapacity(m_items.Length + 8);
m_head--;
if (m_head < 0)
m_head = m_items.Length - 1;
m_items[m_head] = item;
m_size++;
}
}
// must be called from within a lock(m_lock) !
private void SetCapacity(int newCapacity)
{
if (m_size == 0)
{
if (m_size == 0)
{
m_items = new T[newCapacity];
m_head = 0;
return;
}
}
T[] newItems = new T[newCapacity];
if (m_head + m_size - 1 < m_items.Length)
{
Array.Copy(m_items, m_head, newItems, 0, m_size);
}
else
{
Array.Copy(m_items, m_head, newItems, 0, m_items.Length - m_head);
Array.Copy(m_items, 0, newItems, m_items.Length - m_head, (m_size - (m_items.Length - m_head)));
}
m_items = newItems;
m_head = 0;
}
/// <summary>
/// Gets an item from the head of the queue, or returns default(T) if empty
/// </summary>
public bool TryDequeue(out T item)
{
if (m_size == 0)
{
item = default(T);
return false;
}
lock (m_lock)
{
if (m_size == 0)
{
item = default(T);
return false;
}
item = m_items[m_head];
m_items[m_head] = default(T);
m_head = (m_head + 1) % m_items.Length;
m_size--;
return true;
}
}
public T TryPeek(int offset)
{
if (m_size == 0)
return default(T);
lock (m_lock)
{
if (m_size == 0)
return default(T);
return m_items[(m_head + offset) % m_items.Length];
}
}
/// <summary>
/// Determines whether an item is in the queue
/// </summary>
public bool Contains(T item)
{
lock (m_lock)
{
int ptr = m_head;
for (int i = 0; i < m_size; i++)
{
if (m_items[ptr] == null)
{
if (item == null)
return true;
}
else
{
if (m_items[ptr].Equals(item))
return true;
}
ptr = (ptr + 1) % m_items.Length;
}
}
return false;
}
/// <summary>
/// Copies the queue items to a new array
/// </summary>
public T[] ToArray()
{
lock (m_lock)
{
T[] retval = new T[m_size];
int ptr = m_head;
for (int i = 0; i < m_size; i++)
{
retval[i] = m_items[ptr++];
if (ptr >= m_items.Length)
ptr = 0;
}
return retval;
}
}
/// <summary>
/// Removes all objects from the queue
/// </summary>
public void Clear()
{
lock (m_lock)
{
for (int i = 0; i < m_items.Length; i++)
m_items[i] = default(T);
m_head = 0;
m_size = 0;
}
}
}
}

View File

@@ -0,0 +1,370 @@
using System;
namespace Lidgren.Network
{
/// <summary>
/// A fast random number generator for .NET
/// Colin Green, January 2005
/// </summary>
/// September 4th 2005
/// Added NextBytesUnsafe() - commented out by default.
/// Fixed bug in Reinitialise() - y,z and w variables were not being reset.
///
/// Key points:
/// 1) Based on a simple and fast xor-shift pseudo random number generator (RNG) specified in:
/// Marsaglia, George. (2003). Xorshift RNGs.
/// http://www.jstatsoft.org/v08/i14/xorshift.pdf
///
/// This particular implementation of xorshift has a period of 2^128-1. See the above paper to see
/// how this can be easily extened if you need a longer period. At the time of writing I could find no
/// information on the period of System.Random for comparison.
///
/// 2) Faster than System.Random. Up to 8x faster, depending on which methods are called.
///
/// 3) Direct replacement for System.Random. This class implements all of the methods that System.Random
/// does plus some additional methods. The like named methods are functionally equivalent.
///
/// 4) Allows fast re-initialisation with a seed, unlike System.Random which accepts a seed at construction
/// time which then executes a relatively expensive initialisation routine. This provides a vast speed improvement
/// if you need to reset the pseudo-random number sequence many times, e.g. if you want to re-generate the same
/// sequence many times. An alternative might be to cache random numbers in an array, but that approach is limited
/// by memory capacity and the fact that you may also want a large number of different sequences cached. Each sequence
/// can each be represented by a single seed value (int) when using FastRandom.
///
/// Notes.
/// A further performance improvement can be obtained by declaring local variables as static, thus avoiding
/// re-allocation of variables on each call. However care should be taken if multiple instances of
/// FastRandom are in use or if being used in a multi-threaded environment.
public class NetRandom
{
/// <summary>
/// Gets a global NetRandom instance
/// </summary>
public static readonly NetRandom Instance = new NetRandom();
// The +1 ensures NextDouble doesn't generate 1.0
const double REAL_UNIT_INT = 1.0 / ((double)int.MaxValue + 1.0);
const double REAL_UNIT_UINT = 1.0 / ((double)uint.MaxValue + 1.0);
const uint Y = 842502087, Z = 3579807591, W = 273326509;
private static int s_extraSeed = 42;
uint x, y, z, w;
#region Constructors
/// <summary>
/// Initialises a new instance using time dependent seed.
/// </summary>
public NetRandom()
{
// Initialise using the system tick count.
Reinitialise(GetSeed(this));
}
/// <summary>
/// Initialises a new instance using an int value as seed.
/// This constructor signature is provided to maintain compatibility with
/// System.Random
/// </summary>
public NetRandom(int seed)
{
Reinitialise(seed);
}
public int GetSeed(object forObject)
{
// mix some semi-random properties
int seed = (int)Environment.TickCount;
seed ^= forObject.GetHashCode();
//seed ^= (int)(Stopwatch.GetTimestamp());
//seed ^= (int)(Environment.WorkingSet); // will return 0 on mono
int extraSeed = System.Threading.Interlocked.Increment(ref s_extraSeed);
return seed + extraSeed;
}
#endregion
#region Public Methods [Reinitialisation]
/// <summary>
/// Reinitialises using an int value as a seed.
/// </summary>
/// <param name="seed"></param>
public void Reinitialise(int seed)
{
// The only stipulation stated for the xorshift RNG is that at least one of
// the seeds x,y,z,w is non-zero. We fulfill that requirement by only allowing
// resetting of the x seed
x = (uint)seed;
y = Y;
z = Z;
w = W;
}
#endregion
#region Public Methods [System.Random functionally equivalent methods]
/// <summary>
/// Generates a random int over the range 0 to int.MaxValue-1.
/// MaxValue is not generated in order to remain functionally equivalent to System.Random.Next().
/// This does slightly eat into some of the performance gain over System.Random, but not much.
/// For better performance see:
///
/// Call NextInt() for an int over the range 0 to int.MaxValue.
///
/// Call NextUInt() and cast the result to an int to generate an int over the full Int32 value range
/// including negative values.
/// </summary>
/// <returns></returns>
public int Next()
{
uint t = (x ^ (x << 11));
x = y; y = z; z = w;
w = (w ^ (w >> 19)) ^ (t ^ (t >> 8));
// Handle the special case where the value int.MaxValue is generated. This is outside of
// the range of permitted values, so we therefore call Next() to try again.
uint rtn = w & 0x7FFFFFFF;
if (rtn == 0x7FFFFFFF)
return Next();
return (int)rtn;
}
/// <summary>
/// Generates a random int over the range 0 to upperBound-1, and not including upperBound.
/// </summary>
/// <param name="upperBound"></param>
/// <returns></returns>
public int Next(int upperBound)
{
if (upperBound < 0)
throw new ArgumentOutOfRangeException("upperBound", upperBound, "upperBound must be >=0");
uint t = (x ^ (x << 11));
x = y; y = z; z = w;
// The explicit int cast before the first multiplication gives better performance.
// See comments in NextDouble.
return (int)((REAL_UNIT_INT * (int)(0x7FFFFFFF & (w = (w ^ (w >> 19)) ^ (t ^ (t >> 8))))) * upperBound);
}
/// <summary>
/// Generates a random int over the range lowerBound to upperBound-1, and not including upperBound.
/// upperBound must be >= lowerBound. lowerBound may be negative.
/// </summary>
/// <param name="lowerBound"></param>
/// <param name="upperBound"></param>
/// <returns></returns>
public int Next(int lowerBound, int upperBound)
{
if (lowerBound > upperBound)
throw new ArgumentOutOfRangeException("upperBound", upperBound, "upperBound must be >=lowerBound");
uint t = (x ^ (x << 11));
x = y; y = z; z = w;
// The explicit int cast before the first multiplication gives better performance.
// See comments in NextDouble.
int range = upperBound - lowerBound;
if (range < 0)
{ // If range is <0 then an overflow has occured and must resort to using long integer arithmetic instead (slower).
// We also must use all 32 bits of precision, instead of the normal 31, which again is slower.
return lowerBound + (int)((REAL_UNIT_UINT * (double)(w = (w ^ (w >> 19)) ^ (t ^ (t >> 8)))) * (double)((long)upperBound - (long)lowerBound));
}
// 31 bits of precision will suffice if range<=int.MaxValue. This allows us to cast to an int and gain
// a little more performance.
return lowerBound + (int)((REAL_UNIT_INT * (double)(int)(0x7FFFFFFF & (w = (w ^ (w >> 19)) ^ (t ^ (t >> 8))))) * (double)range);
}
/// <summary>
/// Generates a random double. Values returned are from 0.0 up to but not including 1.0.
/// </summary>
/// <returns></returns>
public double NextDouble()
{
uint t = (x ^ (x << 11));
x = y; y = z; z = w;
// Here we can gain a 2x speed improvement by generating a value that can be cast to
// an int instead of the more easily available uint. If we then explicitly cast to an
// int the compiler will then cast the int to a double to perform the multiplication,
// this final cast is a lot faster than casting from a uint to a double. The extra cast
// to an int is very fast (the allocated bits remain the same) and so the overall effect
// of the extra cast is a significant performance improvement.
//
// Also note that the loss of one bit of precision is equivalent to what occurs within
// System.Random.
return (REAL_UNIT_INT * (int)(0x7FFFFFFF & (w = (w ^ (w >> 19)) ^ (t ^ (t >> 8)))));
}
/// <summary>
/// Generates a random single. Values returned are from 0.0 up to but not including 1.0.
/// </summary>
public float NextSingle()
{
return (float)NextDouble();
}
/// <summary>
/// Fills the provided byte array with random bytes.
/// This method is functionally equivalent to System.Random.NextBytes().
/// </summary>
/// <param name="buffer"></param>
public void NextBytes(byte[] buffer)
{
// Fill up the bulk of the buffer in chunks of 4 bytes at a time.
uint x = this.x, y = this.y, z = this.z, w = this.w;
int i = 0;
uint t;
for (int bound = buffer.Length - 3; i < bound; )
{
// Generate 4 bytes.
// Increased performance is achieved by generating 4 random bytes per loop.
// Also note that no mask needs to be applied to zero out the higher order bytes before
// casting because the cast ignores thos bytes. Thanks to Stefan Troschütz for pointing this out.
t = (x ^ (x << 11));
x = y; y = z; z = w;
w = (w ^ (w >> 19)) ^ (t ^ (t >> 8));
buffer[i++] = (byte)w;
buffer[i++] = (byte)(w >> 8);
buffer[i++] = (byte)(w >> 16);
buffer[i++] = (byte)(w >> 24);
}
// Fill up any remaining bytes in the buffer.
if (i < buffer.Length)
{
// Generate 4 bytes.
t = (x ^ (x << 11));
x = y; y = z; z = w;
w = (w ^ (w >> 19)) ^ (t ^ (t >> 8));
buffer[i++] = (byte)w;
if (i < buffer.Length)
{
buffer[i++] = (byte)(w >> 8);
if (i < buffer.Length)
{
buffer[i++] = (byte)(w >> 16);
if (i < buffer.Length)
{
buffer[i] = (byte)(w >> 24);
}
}
}
}
this.x = x; this.y = y; this.z = z; this.w = w;
}
// /// <summary>
// /// A version of NextBytes that uses a pointer to set 4 bytes of the byte buffer in one operation
// /// thus providing a nice speedup. The loop is also partially unrolled to allow out-of-order-execution,
// /// this results in about a x2 speedup on an AMD Athlon. Thus performance may vary wildly on different CPUs
// /// depending on the number of execution units available.
// ///
// /// Another significant speedup is obtained by setting the 4 bytes by indexing pDWord (e.g. pDWord[i++]=w)
// /// instead of adjusting it dereferencing it (e.g. *pDWord++=w).
// ///
// /// Note that this routine requires the unsafe compilation flag to be specified and so is commented out by default.
// /// </summary>
// /// <param name="buffer"></param>
// public unsafe void NextBytesUnsafe(byte[] buffer)
// {
// if(buffer.Length % 8 != 0)
// throw new ArgumentException("Buffer length must be divisible by 8", "buffer");
//
// uint x=this.x, y=this.y, z=this.z, w=this.w;
//
// fixed(byte* pByte0 = buffer)
// {
// uint* pDWord = (uint*)pByte0;
// for(int i=0, len=buffer.Length>>2; i < len; i+=2)
// {
// uint t=(x^(x<<11));
// x=y; y=z; z=w;
// pDWord[i] = w = (w^(w>>19))^(t^(t>>8));
//
// t=(x^(x<<11));
// x=y; y=z; z=w;
// pDWord[i+1] = w = (w^(w>>19))^(t^(t>>8));
// }
// }
//
// this.x=x; this.y=y; this.z=z; this.w=w;
// }
#endregion
#region Public Methods [Methods not present on System.Random]
/// <summary>
/// Generates a uint. Values returned are over the full range of a uint,
/// uint.MinValue to uint.MaxValue, inclusive.
///
/// This is the fastest method for generating a single random number because the underlying
/// random number generator algorithm generates 32 random bits that can be cast directly to
/// a uint.
/// </summary>
[CLSCompliant(false)]
public uint NextUInt()
{
uint t = (x ^ (x << 11));
x = y; y = z; z = w;
return (w = (w ^ (w >> 19)) ^ (t ^ (t >> 8)));
}
/// <summary>
/// Generates a random int over the range 0 to int.MaxValue, inclusive.
/// This method differs from Next() only in that the range is 0 to int.MaxValue
/// and not 0 to int.MaxValue-1.
///
/// The slight difference in range means this method is slightly faster than Next()
/// but is not functionally equivalent to System.Random.Next().
/// </summary>
/// <returns></returns>
public int NextInt()
{
uint t = (x ^ (x << 11));
x = y; y = z; z = w;
return (int)(0x7FFFFFFF & (w = (w ^ (w >> 19)) ^ (t ^ (t >> 8))));
}
// Buffer 32 bits in bitBuffer, return 1 at a time, keep track of how many have been returned
// with bitBufferIdx.
uint bitBuffer;
uint bitMask = 1;
/// <summary>
/// Generates a single random bit.
/// This method's performance is improved by generating 32 bits in one operation and storing them
/// ready for future calls.
/// </summary>
/// <returns></returns>
public bool NextBool()
{
if (bitMask == 1)
{
// Generate 32 more bits.
uint t = (x ^ (x << 11));
x = y; y = z; z = w;
bitBuffer = w = (w ^ (w >> 19)) ^ (t ^ (t >> 8));
// Reset the bitMask that tells us which bit to read next.
bitMask = 0x80000000;
return (bitBuffer & bitMask) == 0;
}
return (bitBuffer & (bitMask >>= 1)) == 0;
}
#endregion
}
}

View File

@@ -0,0 +1,18 @@
using System;
namespace Lidgren.Network
{
internal abstract class NetReceiverChannelBase
{
internal NetPeer m_peer;
internal NetConnection m_connection;
public NetReceiverChannelBase(NetConnection connection)
{
m_connection = connection;
m_peer = connection.m_peer;
}
internal abstract void ReceiveMessage(NetIncomingMessage msg);
}
}

View File

@@ -0,0 +1,87 @@
using System;
namespace Lidgren.Network
{
internal sealed class NetReliableOrderedReceiver : NetReceiverChannelBase
{
private int m_windowStart;
private int m_windowSize;
private NetBitVector m_earlyReceived;
internal NetIncomingMessage[] m_withheldMessages;
public NetReliableOrderedReceiver(NetConnection connection, int windowSize)
: base(connection)
{
m_windowSize = windowSize;
m_withheldMessages = new NetIncomingMessage[windowSize];
m_earlyReceived = new NetBitVector(windowSize);
}
private void AdvanceWindow()
{
m_earlyReceived.Set(m_windowStart % m_windowSize, false);
m_windowStart = (m_windowStart + 1) % NetConstants.NumSequenceNumbers;
}
internal override void ReceiveMessage(NetIncomingMessage message)
{
int relate = NetUtility.RelativeSequenceNumber(message.m_sequenceNumber, m_windowStart);
// ack no matter what
m_connection.QueueAck(message.m_receivedMessageType, message.m_sequenceNumber);
if (relate == 0)
{
// Log("Received message #" + message.SequenceNumber + " right on time");
//
// excellent, right on time
//
m_peer.LogVerbose("Received RIGHT-ON-TIME " + message);
AdvanceWindow();
m_peer.ReleaseMessage(message);
// release withheld messages
int nextSeqNr = (message.m_sequenceNumber + 1) % NetConstants.NumSequenceNumbers;
while (m_earlyReceived[nextSeqNr % m_windowSize])
{
message = m_withheldMessages[nextSeqNr % m_windowSize];
NetException.Assert(message != null);
// remove it from withheld messages
m_withheldMessages[nextSeqNr % m_windowSize] = null;
m_peer.LogVerbose("Releasing withheld message #" + message);
m_peer.ReleaseMessage(message);
AdvanceWindow();
nextSeqNr++;
}
return;
}
if (relate < 0)
{
m_peer.LogVerbose("Received message #" + message.m_sequenceNumber + " DROPPING DUPLICATE");
// duplicate
return;
}
// relate > 0 = early message
if (relate > m_windowSize)
{
// too early message!
m_peer.LogDebug("Received " + message + " TOO EARLY! Expected " + m_windowStart);
return;
}
m_earlyReceived.Set(message.m_sequenceNumber % m_windowSize, true);
m_peer.LogVerbose("Received " + message + " WITHHOLDING, waiting for " + m_windowStart);
m_withheldMessages[message.m_sequenceNumber % m_windowSize] = message;
}
}
}

View File

@@ -0,0 +1,240 @@
using System;
using System.Threading;
namespace Lidgren.Network
{
/// <summary>
/// Sender part of Selective repeat ARQ for a particular NetChannel
/// </summary>
internal sealed class NetReliableSenderChannel : NetSenderChannelBase
{
private NetConnection m_connection;
private int m_windowStart;
private int m_windowSize;
private int m_sendStart;
private NetBitVector m_receivedAcks;
internal NetStoredReliableMessage[] m_storedMessages;
internal float m_resendDelay;
internal override int WindowSize { get { return m_windowSize; } }
internal NetReliableSenderChannel(NetConnection connection, int windowSize)
{
m_connection = connection;
m_windowSize = windowSize;
m_windowStart = 0;
m_sendStart = 0;
m_receivedAcks = new NetBitVector(NetConstants.NumSequenceNumbers);
m_storedMessages = new NetStoredReliableMessage[m_windowSize];
m_queuedSends = new NetQueue<NetOutgoingMessage>(8);
m_resendDelay = m_connection.GetResendDelay();
}
internal override int GetAllowedSends()
{
int retval = m_windowSize - ((m_sendStart + NetConstants.NumSequenceNumbers) - m_windowStart) % NetConstants.NumSequenceNumbers;
NetException.Assert(retval >= 0 && retval <= m_windowSize);
return retval;
}
internal override void Reset()
{
m_receivedAcks.Clear();
for (int i = 0; i < m_storedMessages.Length; i++)
m_storedMessages[i].Reset();
m_queuedSends.Clear();
m_windowStart = 0;
m_sendStart = 0;
}
internal override NetSendResult Enqueue(NetOutgoingMessage message)
{
m_queuedSends.Enqueue(message);
int queueLen = m_queuedSends.Count;
int left = m_windowSize - ((m_sendStart + NetConstants.NumSequenceNumbers) - m_windowStart) % NetConstants.NumSequenceNumbers;
if (queueLen <= left)
return NetSendResult.Sent;
return NetSendResult.Queued;
}
// call this regularely
internal override void SendQueuedMessages(float now)
{
//
// resends
//
for (int i = 0; i < m_storedMessages.Length; i++)
{
NetOutgoingMessage om = m_storedMessages[i].Message;
if (om == null)
continue;
float t = m_storedMessages[i].LastSent;
if (t > 0 && (now - t) > m_resendDelay)
{
// deduce sequence number
int startSlot = m_windowStart % m_windowSize;
int seqNr = m_windowStart;
while (startSlot != i)
{
startSlot--;
if (startSlot < 0)
startSlot = m_windowSize - 1;
seqNr--;
}
m_connection.m_peer.LogVerbose("Resending due to delay #" + seqNr + " " + om.ToString());
m_connection.m_statistics.MessageResent();
m_connection.QueueSendMessage(om, seqNr);
m_storedMessages[i].LastSent = now;
m_storedMessages[i].NumSent++;
}
}
int num = GetAllowedSends();
if (num < 1)
return;
// queued sends
while (m_queuedSends.Count > 0 && num > 0)
{
NetOutgoingMessage om;
if (m_queuedSends.TryDequeue(out om))
ExecuteSend(now, om);
num--;
NetException.Assert(num == GetAllowedSends());
}
}
private void ExecuteSend(float now, NetOutgoingMessage message)
{
int seqNr = m_sendStart;
m_sendStart = (m_sendStart + 1) % NetConstants.NumSequenceNumbers;
m_connection.QueueSendMessage(message, seqNr);
int storeIndex = seqNr % m_windowSize;
NetException.Assert(m_storedMessages[storeIndex].Message == null);
m_storedMessages[storeIndex].NumSent++;
m_storedMessages[storeIndex].Message = message;
m_storedMessages[storeIndex].LastSent = now;
return;
}
private void DestoreMessage(int storeIndex)
{
NetOutgoingMessage storedMessage = m_storedMessages[storeIndex].Message;
NetException.Assert(storedMessage != null);
Interlocked.Decrement(ref storedMessage.m_recyclingCount);
if (storedMessage.m_recyclingCount <= 0)
m_connection.m_peer.Recycle(storedMessage);
m_storedMessages[storeIndex] = new NetStoredReliableMessage();
}
// remoteWindowStart is remote expected sequence number; everything below this has arrived properly
// seqNr is the actual nr received
internal override void ReceiveAcknowledge(float now, int seqNr)
{
// late (dupe), on time or early ack?
int relate = NetUtility.RelativeSequenceNumber(seqNr, m_windowStart);
if (relate < 0)
{
//m_connection.m_peer.LogDebug("Received late/dupe ack for #" + seqNr);
return; // late/duplicate ack
}
if (relate == 0)
{
//m_connection.m_peer.LogDebug("Received right-on-time ack for #" + seqNr);
// ack arrived right on time
NetException.Assert(seqNr == m_windowStart);
m_receivedAcks[m_windowStart] = false;
DestoreMessage(m_windowStart % m_windowSize);
m_windowStart = (m_windowStart + 1) % NetConstants.NumSequenceNumbers;
// advance window if we already have early acks
while (m_receivedAcks[m_windowStart])
{
//m_connection.m_peer.LogDebug("Using early ack for #" + m_windowStart + "...");
m_receivedAcks[m_windowStart] = false;
NetException.Assert(m_storedMessages[m_windowStart % m_windowSize].Message == null); // should already be destored
m_windowStart = (m_windowStart + 1) % NetConstants.NumSequenceNumbers;
//m_connection.m_peer.LogDebug("Advancing window to #" + m_windowStart);
}
return;
}
//
// early ack... (if it has been sent!)
//
// If it has been sent either the m_windowStart message was lost
// ... or the ack for that message was lost
//
//m_connection.m_peer.LogDebug("Received early ack for #" + seqNr);
int sendRelate = NetUtility.RelativeSequenceNumber(seqNr, m_sendStart);
if (sendRelate <= 0)
{
// yes, we've sent this message - it's an early (but valid) ack
if (m_receivedAcks[seqNr])
{
// we've already destored/been acked for this message
}
else
{
DestoreMessage(seqNr % m_windowSize);
m_receivedAcks[seqNr] = true;
}
}
else if (sendRelate > 0)
{
// uh... we haven't sent this message yet? Weird, dupe or error...
return;
}
// Ok, lets resend all missing acks
int rnr = seqNr;
do
{
rnr--;
if (rnr < 0)
rnr = NetConstants.NumSequenceNumbers - 1;
if (m_receivedAcks[rnr])
{
// m_connection.m_peer.LogDebug("Not resending #" + rnr + " (since we got ack)");
}
else
{
int slot = rnr % m_windowSize;
NetException.Assert(m_storedMessages[slot].Message != null);
if (m_storedMessages[slot].NumSent == 1)
{
// just sent once; resend immediately since we found gap in ack sequence
NetOutgoingMessage rmsg = m_storedMessages[slot].Message;
m_connection.m_peer.LogVerbose("Resending #" + rnr + " (" + rmsg + ")");
m_storedMessages[slot].LastSent = now;
m_storedMessages[slot].NumSent++;
m_connection.m_statistics.MessageResent();
m_connection.QueueSendMessage(rmsg, rnr);
}
}
} while (rnr != m_windowStart);
}
}
}

View File

@@ -0,0 +1,63 @@
using System;
namespace Lidgren.Network
{
internal sealed class NetReliableSequencedReceiver : NetReceiverChannelBase
{
private int m_windowStart;
private int m_windowSize;
public NetReliableSequencedReceiver(NetConnection connection, int windowSize)
: base(connection)
{
m_windowSize = windowSize;
}
private void AdvanceWindow()
{
m_windowStart = (m_windowStart + 1) % NetConstants.NumSequenceNumbers;
}
internal override void ReceiveMessage(NetIncomingMessage message)
{
int nr = message.m_sequenceNumber;
int relate = NetUtility.RelativeSequenceNumber(nr, m_windowStart);
// ack no matter what
m_connection.QueueAck(message.m_receivedMessageType, nr);
if (relate == 0)
{
// Log("Received message #" + message.SequenceNumber + " right on time");
//
// excellent, right on time
//
AdvanceWindow();
m_peer.ReleaseMessage(message);
return;
}
if (relate < 0)
{
m_peer.LogVerbose("Received message #" + message.m_sequenceNumber + " DROPPING LATE or DUPE");
return;
}
// relate > 0 = early message
if (relate > m_windowSize)
{
// too early message!
m_peer.LogDebug("Received " + message + " TOO EARLY! Expected " + m_windowStart);
return;
}
// ok
m_windowStart = (m_windowStart + relate) % NetConstants.NumSequenceNumbers;
m_peer.ReleaseMessage(message);
return;
}
}
}

View File

@@ -0,0 +1,87 @@
using System;
namespace Lidgren.Network
{
internal sealed class NetReliableUnorderedReceiver : NetReceiverChannelBase
{
private int m_windowStart;
private int m_windowSize;
private NetBitVector m_earlyReceived;
public NetReliableUnorderedReceiver(NetConnection connection, int windowSize)
: base(connection)
{
m_windowSize = windowSize;
m_earlyReceived = new NetBitVector(windowSize);
}
private void AdvanceWindow()
{
m_earlyReceived.Set(m_windowStart % m_windowSize, false);
m_windowStart = (m_windowStart + 1) % NetConstants.NumSequenceNumbers;
}
internal override void ReceiveMessage(NetIncomingMessage message)
{
int relate = NetUtility.RelativeSequenceNumber(message.m_sequenceNumber, m_windowStart);
// ack no matter what
m_connection.QueueAck(message.m_receivedMessageType, message.m_sequenceNumber);
if (relate == 0)
{
// Log("Received message #" + message.SequenceNumber + " right on time");
//
// excellent, right on time
//
m_peer.LogVerbose("Received RIGHT-ON-TIME " + message);
AdvanceWindow();
m_peer.ReleaseMessage(message);
// release withheld messages
int nextSeqNr = (message.m_sequenceNumber + 1) % NetConstants.NumSequenceNumbers;
while (m_earlyReceived[nextSeqNr % m_windowSize])
{
//message = m_withheldMessages[nextSeqNr % m_windowSize];
//NetException.Assert(message != null);
// remove it from withheld messages
//m_withheldMessages[nextSeqNr % m_windowSize] = null;
//m_peer.LogVerbose("Releasing withheld message #" + message);
//m_peer.ReleaseMessage(message);
AdvanceWindow();
nextSeqNr++;
}
return;
}
if (relate < 0)
{
// duplicate
m_peer.LogVerbose("Received message #" + message.m_sequenceNumber + " DROPPING DUPLICATE");
return;
}
// relate > 0 = early message
if (relate > m_windowSize)
{
// too early message!
m_peer.LogDebug("Received " + message + " TOO EARLY! Expected " + m_windowStart);
return;
}
m_earlyReceived.Set(message.m_sequenceNumber % m_windowSize, true);
//m_peer.LogVerbose("Received " + message + " WITHHOLDING, waiting for " + m_windowStart);
//m_withheldMessages[message.m_sequenceNumber % m_windowSize] = message;
m_peer.ReleaseMessage(message);
}
}
}

View File

@@ -0,0 +1,30 @@
using System;
namespace Lidgren.Network
{
/// <summary>
/// Result of a SendMessage call
/// </summary>
public enum NetSendResult
{
/// <summary>
/// Message failed to enqueue; for example if there's no connection in place
/// </summary>
Failed = 0,
/// <summary>
/// Message was immediately sent
/// </summary>
Sent = 1,
/// <summary>
/// Message was queued for delivery
/// </summary>
Queued = 2,
/// <summary>
/// Message was dropped immediately since too many message were queued
/// </summary>
Dropped = 3
}
}

View File

@@ -0,0 +1,19 @@
using System;
namespace Lidgren.Network
{
internal abstract class NetSenderChannelBase
{
// access this directly to queue things in this channel
internal NetQueue<NetOutgoingMessage> m_queuedSends;
internal abstract int WindowSize { get; }
internal abstract int GetAllowedSends();
internal abstract NetSendResult Enqueue(NetOutgoingMessage message);
internal abstract void SendQueuedMessages(float now);
internal abstract void Reset();
internal abstract void ReceiveAcknowledge(float now, int sequenceNumber);
}
}

View File

@@ -0,0 +1,24 @@
using System;
namespace Lidgren.Network
{
/// <summary>
/// Specialized version of NetPeer used for "server" peers
/// </summary>
public class NetServer : NetPeer
{
public NetServer(NetPeerConfiguration config)
: base(config)
{
config.AcceptIncomingConnections = true;
}
/// <summary>
/// Returns a string that represents this object
/// </summary>
public override string ToString()
{
return "[NetServer " + ConnectionsCount + " connections]";
}
}
}

View File

@@ -0,0 +1,18 @@
using System;
namespace Lidgren.Network
{
internal struct NetStoredReliableMessage
{
public int NumSent;
public float LastSent;
public NetOutgoingMessage Message;
public void Reset()
{
NumSent = 0;
LastSent = 0;
Message = null;
}
}
}

View File

@@ -0,0 +1,59 @@
/* Copyright (c) 2010 Michael Lidgren
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.
*/
#define IS_STOPWATCH_AVAILABLE
using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
namespace Lidgren.Network
{
/// <summary>
/// Time service
/// </summary>
public static class NetTime
{
#if IS_STOPWATCH_AVAILABLE
private static readonly long s_timeInitialized = Stopwatch.GetTimestamp();
private static readonly double s_dInvFreq = 1.0 / (double)Stopwatch.Frequency;
/// <summary>
/// Get number of seconds since the application started
/// </summary>
public static double Now { get { return (double)(Stopwatch.GetTimestamp() - s_timeInitialized) * s_dInvFreq; } }
#else
/// <summary>
/// Get number of seconds since the application started
/// </summary>
public static double Now { get { return (double)Environment.TickCount / 1000.0; } }
#endif
/// <summary>
/// Given seconds it will output a human friendly readable string (milliseconds if less than 60 seconds)
/// </summary>
public static string ToReadable(double seconds)
{
if (seconds > 60)
return TimeSpan.FromSeconds(seconds).ToString();
return (seconds * 1000.0).ToString("N2") + " ms";
}
}
}

View File

@@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Lidgren.Network
{
// replace with BCL 4.0 Tuple<> when appropriate
internal struct NetTuple<A, B>
{
public A Item1;
public B Item2;
public NetTuple(A item1, B item2)
{
Item1 = item1;
Item2 = item2;
}
}
}

View File

@@ -0,0 +1,125 @@
using System;
using System.Threading;
namespace Lidgren.Network
{
/// <summary>
/// Sender part of Selective repeat ARQ for a particular NetChannel
/// </summary>
internal sealed class NetUnreliableSenderChannel : NetSenderChannelBase
{
private NetConnection m_connection;
private int m_windowStart;
private int m_windowSize;
private int m_sendStart;
private NetBitVector m_receivedAcks;
internal override int WindowSize { get { return m_windowSize; } }
internal NetUnreliableSenderChannel(NetConnection connection, int windowSize)
{
m_connection = connection;
m_windowSize = windowSize;
m_windowStart = 0;
m_sendStart = 0;
m_receivedAcks = new NetBitVector(NetConstants.NumSequenceNumbers);
m_queuedSends = new NetQueue<NetOutgoingMessage>(8);
}
internal override int GetAllowedSends()
{
int retval = m_windowSize - ((m_sendStart + NetConstants.NumSequenceNumbers) - m_windowStart) % m_windowSize;
NetException.Assert(retval >= 0 && retval <= m_windowSize);
return retval;
}
internal override void Reset()
{
m_receivedAcks.Clear();
m_queuedSends.Clear();
m_windowStart = 0;
m_sendStart = 0;
}
internal override NetSendResult Enqueue(NetOutgoingMessage message)
{
int queueLen = m_queuedSends.Count + 1;
int left = m_windowSize - ((m_sendStart + NetConstants.NumSequenceNumbers) - m_windowStart) % NetConstants.NumSequenceNumbers;
if (queueLen > left)
return NetSendResult.Dropped;
m_queuedSends.Enqueue(message);
return NetSendResult.Sent;
}
// call this regularely
internal override void SendQueuedMessages(float now)
{
int num = GetAllowedSends();
if (num < 1)
return;
// queued sends
while (m_queuedSends.Count > 0 && num > 0)
{
NetOutgoingMessage om;
if (m_queuedSends.TryDequeue(out om))
ExecuteSend(now, om);
num--;
}
}
private void ExecuteSend(float now, NetOutgoingMessage message)
{
m_connection.m_peer.VerifyNetworkThread();
int seqNr = m_sendStart;
m_sendStart = (m_sendStart + 1) % NetConstants.NumSequenceNumbers;
m_connection.QueueSendMessage(message, seqNr);
Interlocked.Decrement(ref message.m_recyclingCount);
if (message.m_recyclingCount <= 0)
m_connection.m_peer.Recycle(message);
return;
}
// remoteWindowStart is remote expected sequence number; everything below this has arrived properly
// seqNr is the actual nr received
internal override void ReceiveAcknowledge(float now, int seqNr)
{
// late (dupe), on time or early ack?
int relate = NetUtility.RelativeSequenceNumber(seqNr, m_windowStart);
if (relate < 0)
{
//m_connection.m_peer.LogDebug("Received late/dupe ack for #" + seqNr);
return; // late/duplicate ack
}
if (relate == 0)
{
//m_connection.m_peer.LogDebug("Received right-on-time ack for #" + seqNr);
// ack arrived right on time
NetException.Assert(seqNr == m_windowStart);
m_receivedAcks[m_windowStart] = false;
m_windowStart = (m_windowStart + 1) % NetConstants.NumSequenceNumbers;
return;
}
// Advance window to this position
m_receivedAcks[seqNr] = true;
while (m_windowStart != seqNr)
{
m_receivedAcks[m_windowStart] = false;
m_windowStart = (m_windowStart + 1) % NetConstants.NumSequenceNumbers;
}
}
}
}

View File

@@ -0,0 +1,29 @@
using System;
namespace Lidgren.Network
{
internal sealed class NetUnreliableSequencedReceiver : NetReceiverChannelBase
{
private int m_lastReceivedSequenceNumber;
public NetUnreliableSequencedReceiver(NetConnection connection)
: base(connection)
{
}
internal override void ReceiveMessage(NetIncomingMessage msg)
{
int nr = msg.m_sequenceNumber;
// ack no matter what
m_connection.QueueAck(msg.m_receivedMessageType, nr);
int relate = NetUtility.RelativeSequenceNumber(nr, m_lastReceivedSequenceNumber);
if (relate < 0)
return; // drop if late
m_lastReceivedSequenceNumber = nr;
m_peer.ReleaseMessage(msg);
}
}
}

View File

@@ -0,0 +1,20 @@
using System;
namespace Lidgren.Network
{
internal sealed class NetUnreliableUnorderedReceiver : NetReceiverChannelBase
{
public NetUnreliableUnorderedReceiver(NetConnection connection)
: base(connection)
{
}
internal override void ReceiveMessage(NetIncomingMessage msg)
{
// ack no matter what
m_connection.QueueAck(msg.m_receivedMessageType, msg.m_sequenceNumber);
m_peer.ReleaseMessage(msg);
}
}
}

View File

@@ -0,0 +1,338 @@
/* Copyright (c) 2010 Michael Lidgren
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.
*/
using System;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;
namespace Lidgren.Network
{
/// <summary>
/// Utility methods
/// </summary>
public static class NetUtility
{
/// <summary>
/// Get IPv4 endpoint from notation (xxx.xxx.xxx.xxx) or hostname and port number
/// </summary>
public static IPEndPoint Resolve(string ipOrHost, int port)
{
IPAddress adr = Resolve(ipOrHost);
return new IPEndPoint(adr, port);
}
/// <summary>
/// Get IPv4 address from notation (xxx.xxx.xxx.xxx) or hostname
/// </summary>
public static IPAddress Resolve(string ipOrHost)
{
if (string.IsNullOrEmpty(ipOrHost))
throw new ArgumentException("Supplied string must not be empty", "ipOrHost");
ipOrHost = ipOrHost.Trim();
IPAddress ipAddress = null;
if (IPAddress.TryParse(ipOrHost, out ipAddress))
{
if (ipAddress.AddressFamily == AddressFamily.InterNetwork)
return ipAddress;
throw new ArgumentException("This method will not currently resolve other than ipv4 addresses");
}
// ok must be a host name
IPHostEntry entry;
try
{
entry = Dns.GetHostEntry(ipOrHost);
if (entry == null)
return null;
// check each entry for a valid IP address
foreach (IPAddress ipCurrent in entry.AddressList)
{
if (ipCurrent.AddressFamily == AddressFamily.InterNetwork)
return ipCurrent;
}
return null;
}
catch (SocketException ex)
{
if (ex.SocketErrorCode == SocketError.HostNotFound)
{
//LogWrite(string.Format(CultureInfo.InvariantCulture, "Failed to resolve host '{0}'.", ipOrHost));
return null;
}
else
{
throw;
}
}
}
private static NetworkInterface GetNetworkInterface()
{
IPGlobalProperties computerProperties = IPGlobalProperties.GetIPGlobalProperties();
if (computerProperties == null)
return null;
NetworkInterface[] nics = NetworkInterface.GetAllNetworkInterfaces();
if (nics == null || nics.Length < 1)
return null;
NetworkInterface best = null;
foreach (NetworkInterface adapter in nics)
{
if (adapter.NetworkInterfaceType == NetworkInterfaceType.Loopback || adapter.NetworkInterfaceType == NetworkInterfaceType.Unknown)
continue;
if (!adapter.Supports(NetworkInterfaceComponent.IPv4))
continue;
if (best == null)
best = adapter;
if (adapter.OperationalStatus != OperationalStatus.Up)
continue;
// A computer could have several adapters (more than one network card)
// here but just return the first one for now...
return adapter;
}
return best;
}
public static PhysicalAddress GetMacAddress()
{
NetworkInterface ni = GetNetworkInterface();
if (ni == null)
return null;
return ni.GetPhysicalAddress();
}
public static string ToHexString(long data)
{
return ToHexString(BitConverter.GetBytes(data));
}
public static string ToHexString(byte[] data)
{
char[] c = new char[data.Length * 2];
byte b;
for (int i = 0; i < data.Length; ++i)
{
b = ((byte)(data[i] >> 4));
c[i * 2] = (char)(b > 9 ? b + 0x37 : b + 0x30);
b = ((byte)(data[i] & 0xF));
c[i * 2 + 1] = (char)(b > 9 ? b + 0x37 : b + 0x30);
}
return new string(c);
}
/// <summary>
/// Gets my local IP address (not necessarily external) and subnet mask
/// </summary>
public static IPAddress GetMyAddress(out IPAddress mask)
{
NetworkInterface ni = GetNetworkInterface();
if (ni == null)
{
mask = null;
return null;
}
IPInterfaceProperties properties = ni.GetIPProperties();
foreach (UnicastIPAddressInformation unicastAddress in properties.UnicastAddresses)
{
if (unicastAddress != null && unicastAddress.Address != null && unicastAddress.Address.AddressFamily == AddressFamily.InterNetwork)
{
mask = unicastAddress.IPv4Mask;
return unicastAddress.Address;
}
}
mask = null;
return null;
}
/// <summary>
/// Returns true if the IPEndPoint supplied is on the same subnet as this host
/// </summary>
public static bool IsLocal(IPEndPoint endpoint)
{
if (endpoint == null)
return false;
return IsLocal(endpoint.Address);
}
/// <summary>
/// Returns true if the IPAddress supplied is on the same subnet as this host
/// </summary>
public static bool IsLocal(IPAddress remote)
{
IPAddress mask;
IPAddress local = GetMyAddress(out mask);
if (mask == null)
return false;
uint maskBits = BitConverter.ToUInt32(mask.GetAddressBytes(), 0);
uint remoteBits = BitConverter.ToUInt32(remote.GetAddressBytes(), 0);
uint localBits = BitConverter.ToUInt32(local.GetAddressBytes(), 0);
// compare network portions
return ((remoteBits & maskBits) == (localBits & maskBits));
}
/// <summary>
/// Returns how many bits are necessary to hold a certain number
/// </summary>
[CLSCompliant(false)]
public static int BitsToHoldUInt(uint value)
{
int bits = 1;
while ((value >>= 1) != 0)
bits++;
return bits;
}
/// <summary>
/// Returns how many bytes are required to hold a certain number of bits
/// </summary>
public static int BytesToHoldBits(int numBits)
{
return (numBits + 7) / 8;
}
internal static UInt32 SwapByteOrder(UInt32 value)
{
return
((value & 0xff000000) >> 24) |
((value & 0x00ff0000) >> 8) |
((value & 0x0000ff00) << 8) |
((value & 0x000000ff) << 24);
}
internal static UInt64 SwapByteOrder(UInt64 value)
{
return
((value & 0xff00000000000000L) >> 56) |
((value & 0x00ff000000000000L) >> 40) |
((value & 0x0000ff0000000000L) >> 24) |
((value & 0x000000ff00000000L) >> 8) |
((value & 0x00000000ff000000L) << 8) |
((value & 0x0000000000ff0000L) << 24) |
((value & 0x000000000000ff00L) << 40) |
((value & 0x00000000000000ffL) << 56);
}
internal static bool CompareElements(byte[] one, byte[] two)
{
if (one.Length != two.Length)
return false;
for (int i = 0; i < one.Length; i++)
if (one[i] != two[i])
return false;
return true;
}
/// <summary>
/// Convert a hexadecimal string to a byte array
/// </summary>
public static byte[] ToByteArray(String hexString)
{
byte[] retval = new byte[hexString.Length / 2];
for (int i = 0; i < hexString.Length; i += 2)
retval[i / 2] = Convert.ToByte(hexString.Substring(i, 2), 16);
return retval;
}
/// <summary>
/// Converts a number of bytes to a shorter, more readable string representation
/// </summary>
public static string ToHumanReadable(long bytes)
{
if (bytes < 4000) // 1-4 kb is printed in bytes
return bytes + " bytes";
if (bytes < 1000 * 1000) // 4-999 kb is printed in kb
return Math.Round(((double)bytes / 1000.0), 2) + " kilobytes";
return Math.Round(((double)bytes / (1000.0 * 1000.0)), 2) + " megabytes"; // else megabytes
}
internal static int RelativeSequenceNumber(int nr, int expected)
{
int retval = ((nr + NetConstants.NumSequenceNumbers) - expected) % NetConstants.NumSequenceNumbers;
if (retval > (NetConstants.NumSequenceNumbers / 2))
retval -= NetConstants.NumSequenceNumbers;
return retval;
}
// shell sort
internal static void SortMembersList(System.Reflection.MemberInfo[] list)
{
int h;
int j;
System.Reflection.MemberInfo tmp;
h = 1;
while (h * 3 + 1 <= list.Length)
h = 3 * h + 1;
while (h > 0)
{
for (int i = h - 1; i < list.Length; i++)
{
tmp = list[i];
j = i;
while (true)
{
if (j >= h)
{
if (string.Compare(list[j - h].Name, tmp.Name, StringComparison.InvariantCulture) > 0)
{
list[j] = list[j - h];
j -= h;
}
else
break;
}
else
break;
}
list[j] = tmp;
}
h /= 3;
}
}
internal static NetDeliveryMethod GetDeliveryMethod(NetMessageType mtp)
{
if (mtp >= NetMessageType.UserReliableOrdered1)
return NetDeliveryMethod.ReliableOrdered;
else if (mtp >= NetMessageType.UserReliableSequenced1)
return NetDeliveryMethod.ReliableSequenced;
else if (mtp >= NetMessageType.UserReliableUnordered)
return NetDeliveryMethod.ReliableUnordered;
else if (mtp >= NetMessageType.UserSequenced1)
return NetDeliveryMethod.UnreliableSequenced;
return NetDeliveryMethod.Unreliable;
}
}
}

View File

@@ -0,0 +1,37 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Lidgren.Network")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Microsoft")]
[assembly: AssemblyProduct("Lidgren.Network")]
[assembly: AssemblyCopyright("Copyright © Microsoft 2010")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("c4914374-a2fb-4e56-bf94-60d4b81c10a1")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("2010.7.15.0")]
[assembly: AssemblyFileVersion("2010.7.15.0")]
[assembly: System.CLSCompliant(true)]

View File

@@ -0,0 +1,11 @@
using System;
namespace Lidgren.Network
{
internal abstract class SenderChannelBase
{
internal abstract NetSendResult Send(float now, NetOutgoingMessage message);
internal abstract void SendQueuedMessages(float now);
internal abstract void Reset();
}
}

View File

@@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using System.Text;
using Mogre;
//Helper class for the ui. Simple rectangle w/ some useful functions to figure out if the mouse is in it etc.
//Assumes origin (0,0) is top left.
namespace SS3D.HelperClasses
{
public class Rect2D
{
public Vector2 size;
public Vector2 location;
public Rect2D(Vector2 Location, Vector2 Size)
{
size = Size;
location = Location;
}
public bool ContainsPoint(float x, float y)
{
float left = location.x;
float right = location.x + size.x;
float top = location.y;
float bottom = location.y + size.y;
return x > left && x < right && y < bottom && y > top;
}
public bool ContainsRect2D(Rect2D rect2)
{
float left = location.x;
float right = location.x + size.x;
float top = location.y;
float bottom = location.y + size.y;
float left2 = rect2.location.x;
float right2 = rect2.location.x + rect2.size.x;
float top2 = rect2.location.y;
float bottom2 = rect2.location.y + rect2.size.y;
if (bottom < top2) return (false);
if (top > bottom2) return (false);
if (right < left2) return (false);
if (left > right2) return (false);
return (true);
}
}
}

View File

@@ -0,0 +1,175 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
using Mogre;
using Miyagi;
using Miyagi.Common.Resources;
namespace SS3D.Modules
{
public sealed class ConfigManager
{
public Configuration Configuration;
private string ConfigFile;
private const string ResourceGroupName = "Default";
static readonly ConfigManager singleton = new ConfigManager();
static ConfigManager()
{
}
ConfigManager()
{
}
public static ConfigManager Singleton
{
get
{
return singleton;
}
}
public void Initialize(string ConfigFileLoc)
{
if (File.Exists(ConfigFileLoc))
{
System.Xml.Serialization.XmlSerializer ConfigLoader = new System.Xml.Serialization.XmlSerializer(typeof(Configuration));
StreamReader ConfigReader = File.OpenText(ConfigFileLoc);
Configuration Config = (Configuration)ConfigLoader.Deserialize(ConfigReader);
ConfigReader.Close();
Configuration = Config;
ConfigFile = ConfigFileLoc;
}
else
{
if (LogManager.Singleton != null) LogManager.Singleton.LogMessage("ConfigManager: Could not load config. File not found. " + ConfigFileLoc);
}
}
public void Save()
{
if (Configuration == null)
{
if (LogManager.Singleton != null) LogManager.Singleton.LogMessage("ConfigManager: Could not write config. No File loaded. " + Configuration.ToString() + " , " + ConfigFile);
return;
}
else
{
System.Xml.Serialization.XmlSerializer ConfigSaver = new System.Xml.Serialization.XmlSerializer(Configuration.GetType());
StreamWriter ConfigWriter = File.CreateText(ConfigFile);
ConfigSaver.Serialize(ConfigWriter, Configuration);
ConfigWriter.Flush();
ConfigWriter.Close();
}
}
public void LoadResources()
{
if (!ResourceGroupManager.Singleton.ResourceGroupExists(ResourceGroupName)) ResourceGroupManager.Singleton.CreateResourceGroup(ResourceGroupName);
if (Configuration == null || Configuration.Resources.Count == 0)
{
if (LogManager.Singleton != null) LogManager.Singleton.LogMessage("ConfigManager: Could not load ressources. No config loaded or no ressources specified. Config: " + Configuration.ToString() + ", Ressources: " + Configuration.Resources.Count.ToString());
return;
}
else
{
#region General Resources
foreach (string ResLoc in Configuration.Resources)
{
if (Directory.Exists(ResLoc))
{
ResourceGroupManager.Singleton.AddResourceLocation(ResLoc, "FileSystem", ResourceGroupName);
continue;
}
else if (File.Exists(ResLoc))
{
ResourceGroupManager.Singleton.AddResourceLocation(ResLoc, "Zip", ResourceGroupName);
continue;
}
}
InitGroup(ResourceGroupName);
LoadGroup(ResourceGroupName);
#endregion
#region Miyagi Skins
var skins = new List<Skin>();
foreach (string MSkinLoc in Configuration.MiyagiSkins)
{
if (Directory.Exists(MSkinLoc))
{
string[] Files = Directory.GetFiles(MSkinLoc, "*.xml");
foreach (string fontFile in Files)
{
skins.AddRange(Skin.CreateFromXml(Path.Combine(MSkinLoc, fontFile), MiyagiResources.Singleton.mMiyagiSystem));
}
continue;
}
else if (File.Exists(MSkinLoc))
{
skins.AddRange(Skin.CreateFromXml(MSkinLoc, MiyagiResources.Singleton.mMiyagiSystem));
continue;
}
}
MiyagiResources.Singleton.Skins = skins.ToDictionary(s => s.Name);
#endregion
#region Miyagi Fonts
var fonts = new[]
{
TrueTypeFont.CreateFromXml(Configuration.MiyagiTrueTypeFonts, MiyagiResources.Singleton.mMiyagiSystem)
.Cast<Miyagi.Common.Resources.Font>().ToDictionary(f => f.Name),
ImageFont.CreateFromXml(Configuration.MiyagiImageFonts, MiyagiResources.Singleton.mMiyagiSystem)
.Cast<Miyagi.Common.Resources.Font>().ToDictionary(f => f.Name)
};
MiyagiResources.Singleton.Fonts = fonts.SelectMany(dict => dict).ToDictionary(pair => pair.Key, pair => pair.Value);
Miyagi.Common.Resources.Font.Default = MiyagiResources.Singleton.Fonts["BlueHighway"]; //This needs to be the default because we can't change dialog box fonts.
#endregion
}
}
public void InitGroup(string _groupName)
{
if (!ResourceGroupManager.Singleton.IsResourceGroupInitialised(_groupName))
ResourceGroupManager.Singleton.InitialiseResourceGroup(_groupName);
}
public void LoadGroup(string _groupName)
{
if (!ResourceGroupManager.Singleton.IsResourceGroupLoaded(_groupName))
ResourceGroupManager.Singleton.LoadResourceGroup(_groupName);
}
}
[Serializable]
public class Configuration
{
const int _Version = 1;
public uint DisplayWidth = 1024;
public uint DisplayHeight = 768;
public bool Fullscreen = false;
public bool VSync = true;
public int FSAA = 0;
public int TextureFiltering = (int)TextureFilterOptions.TFO_ANISOTROPIC;
public int AnisotropicLevel = 8;
public int NumMipmaps = 8;
public List<string> Resources = new List<string>();
public string MiyagiTrueTypeFonts = "";
public string MiyagiImageFonts = "";
public List<string> MiyagiSkins = new List<string>();
}
}

View File

@@ -0,0 +1,160 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Lidgren.Network;
using SS3D_shared;
using Mogre;
namespace SS3D.Modules.Items
{
public class ItemManager
{
private Map.Map map;
private OgreManager mEngine;
private Network.NetworkManager networkManager;
private Dictionary<ushort, Item> itemDict; // ItemID, Item
private List<ushort> itemsToMove;
private List<ushort> itemsToStop;
private string itemAssemblyName;
private DateTime lastItemUpdate = DateTime.Now;
private double itemUpdateTime = 20;
private double serverUpdateTime = 100;
public ItemManager(OgreManager _mEngine, Map.Map _map, Network.NetworkManager _networkManager)
{
mEngine = _mEngine;
map = _map;
networkManager = _networkManager;
itemDict = new Dictionary<ushort, Item>();
itemAssemblyName = typeof(SS3D_shared.Item).Assembly.ToString();
itemsToMove = new List<ushort>();
itemsToStop = new List<ushort>();
}
public void HandleNetworkMessage(NetIncomingMessage message)
{
ItemMessage messageType = (ItemMessage)message.ReadByte();
switch (messageType)
{
case ItemMessage.CreateItem:
HandleCreateItem(message);
break;
case ItemMessage.InterpolationPacket:
HandleInterpolationPacket(message);
break;
default:
break;
}
}
public void Update()
{
TimeSpan updateSpan = DateTime.Now - lastItemUpdate;
if (updateSpan.TotalMilliseconds > itemUpdateTime)
{
foreach (ushort itemID in itemsToMove)
{
UpdateItemPosition(itemID);
}
lastItemUpdate = DateTime.Now;
foreach (ushort itemId in itemsToStop)
{
itemsToMove.Remove(itemId);
}
itemsToStop.Clear();
}
}
public void Shutdown()
{
foreach (Item item in itemDict.Values)
{
mEngine.SceneMgr.DestroyEntity(item.Entity);
mEngine.SceneMgr.DestroySceneNode(item.Node);
}
itemDict = null;
itemsToMove = null;
itemsToStop = null;
}
#region Item moving
private void HandleInterpolationPacket(NetIncomingMessage msg)
{
ushort itemID = msg.ReadUInt16();
float x = msg.ReadFloat();
float y = msg.ReadFloat();
float z = msg.ReadFloat();
SS3D_shared.HelperClasses.InterpolationPacket intPacket = new SS3D_shared.HelperClasses.InterpolationPacket(x,y,z,0,0,0);
itemDict[itemID].interpolationPacket.Add(intPacket);
if (itemDict[itemID].interpolationPacket.Count > 2)
{
itemDict[itemID].interpolationPacket.RemoveAt(0);
}
itemDict[itemID].lastUpdate = DateTime.Now;
if(!itemsToMove.Contains(itemID))
{
itemsToMove.Add(itemID);
}
}
private void UpdateItemPosition(ushort itemID)
{
TimeSpan timeSinceUpdate = DateTime.Now - itemDict[itemID].lastUpdate;
if (timeSinceUpdate.TotalMilliseconds > serverUpdateTime*4 )
{
itemsToStop.Add(itemID);
return;
}
Vector3 difference;
if (itemDict[itemID].interpolationPacket.Count < 2)
{
difference = itemDict[itemID].interpolationPacket[0].position - itemDict[itemID].Node.Position;
}
else
{
difference = itemDict[itemID].interpolationPacket[1].position - itemDict[itemID].interpolationPacket[0].position;
}
difference /= (float)(serverUpdateTime / itemUpdateTime);
itemDict[itemID].Node.Position += difference;
}
#endregion
#region Item creation
private void HandleCreateItem(NetIncomingMessage msg)
{
string itemName = msg.ReadString();
ushort itemID = msg.ReadUInt16();
float x = msg.ReadFloat();
float y = msg.ReadFloat();
float z = msg.ReadFloat();
Type itemType = Type.GetType(itemName + "," + itemAssemblyName);
object[] args = new object[3];
args[0] = mEngine.SceneMgr;
args[1] = new Vector3(x, y, z);
args[2] = itemID;
object newItem = Activator.CreateInstance(itemType, args);
itemDict[itemID] = (SS3D_shared.Item)newItem;
}
public void SendCreateItem(Vector3 pos)
{
string name = typeof(Crowbar).FullName;
NetOutgoingMessage message = networkManager.GetMessage();
message.Write((byte)NetMessage.ItemMessage);
message.Write((byte)ItemMessage.CreateItem);
message.Write(name);
message.Write(pos.x);
message.Write(pos.y);
message.Write(pos.z);
mEngine.mNetworkMgr.SendMessage(message, NetDeliveryMethod.ReliableOrdered);
}
#endregion
}
}

View File

@@ -0,0 +1,581 @@
using System;
using System.Collections.Generic;
using System.Text;
using Mogre;
using SS3D.HelperClasses;
using SS3D_shared;
namespace SS3D.Modules.Map
{
public class Map
{
#region Variables
public BaseTile[,] tileArray; // The array holding all the tiles that make up the map
public int mapWidth; // Number of tiles across the map (must be a multiple of StaticGeoSize)
public int mapHeight; // Number of tiles up the map (must be a multiple of StaticGeoSize)
public int tileSpacing = 32; // Distance between tiles
public AxisAlignedBox[,] boundingBoxArray;
private OgreManager mEngine;
private geometryMeshManager meshManager; // Builds and stores the meshes for all floors/walls.
/* The static geometry makes up the map - all the walls and floors.
* It is very fast to render but cannot be moved or edited once built.
* It has been split into 10x10 chunks which are assigned automatically, meaning
* if we want to replace/change a tile, we only have to rebuild a small are rather than
* rebuild the thousands of tiles that make up the whole level.
*/
public StaticGeometry[,] staticGeometry;
private int StaticGeoX; // The width of the array - the number of tiles / 10 rounded up.
private int StaticGeoZ; // Same as above.
private int StaticGeoSize = 10; // Size of one side of the square of tiles stored by each staticgeometry in the array.
private Vector2 lastCamGeoPos = Vector2.UNIT_SCALE;
#endregion
public Map(OgreManager _mEngine)
{
mEngine = _mEngine;
}
#region Startup / Loading
public bool InitMap(int width, int height, bool wallSurround, bool startBlank, int partitionSize)
{
meshManager = new geometryMeshManager();
meshManager.CreateMeshes();
mapWidth = width;
mapHeight = height;
float geoSize = StaticGeoSize;
// Get the width and height our staticgeometry array needs to be.
StaticGeoX = (int)System.Math.Ceiling(width / geoSize);
StaticGeoZ = (int)System.Math.Ceiling(height / geoSize);
InitStaticgeometry();
// Init our tileArray.
tileArray = new BaseTile[mapWidth, mapHeight];
boundingBoxArray = new AxisAlignedBox[mapWidth, mapHeight];
for (int x = 0; x < mapWidth; x++)
{
for (int z = 0; z < mapHeight; z++)
{
// The position of the tiles scenenode.
int posX = x * tileSpacing;
int posZ = z * tileSpacing;
if (startBlank)
{
tileArray[x, z] = GenerateNewTile(TileType.Space, new Vector3(posX, 0, posZ));
}
else if ((wallSurround && (x == 0 || (x == mapWidth - 1) || z == 0 || (z == mapHeight - 1))) ||
(partitionSize > 0 && ((x > 0 && x % partitionSize == 0) || (z > 0 && z % partitionSize == 0))))
{
tileArray[x,z] = GenerateNewTile(TileType.Wall, new Vector3(posX, 0, posZ));
}
else
{
tileArray[x, z] = GenerateNewTile(TileType.Floor, new Vector3(posX, 0, posZ));
}
// Get which piece of the staticGeometry array this tile belongs in (automatically
// worked out when the tile is created)
int GeoX = tileArray[x,z].GeoPosX;
int GeoZ = tileArray[x,z].GeoPosZ;
// Add it to the appropriate staticgeometry array.
staticGeometry[GeoX, GeoZ].AddSceneNode(tileArray[x,z].Node);
// Remove it from the main scene manager, otherwise it would be draw twice.
//mEngine.SceneMgr.RootSceneNode.RemoveChild(tileArray[x, z].tileNode);
tileArray[x, z].Node.SetVisible(false);
}
}
// Build all the staticgeometrys.
BuildAllgeometry();
return true;
}
public bool LoadMapSaverMap(int width, int height, string[,] savedArray)
{
meshManager = new geometryMeshManager();
meshManager.CreateMeshes();
mapWidth = width;
mapHeight = height;
float geoSize = StaticGeoSize;
// Get the width and height our staticgeometry array needs to be.
StaticGeoX = (int)System.Math.Ceiling(width / geoSize);
StaticGeoZ = (int)System.Math.Ceiling(height / geoSize);
InitStaticgeometry();
ParseNameArray(savedArray);
for (int x = 0; x < mapWidth; x++)
{
for (int z = 0; z < mapHeight; z++)
{
// Get which piece of the staticGeometry array this tile belongs in (automatically
// worked out when the tile is created)
int GeoX = tileArray[x, z].GeoPosX;
int GeoZ = tileArray[x, z].GeoPosZ;
// Add it to the appropriate staticgeometry array.
staticGeometry[GeoX, GeoZ].AddSceneNode(tileArray[x, z].Node);
}
}
// Build all the staticgeometrys.
BuildAllgeometry();
mEngine.Update();
return true;
}
public bool LoadNetworkedMap(TileType[,] networkedArray, int _mapWidth, int _mapHeight)
{
meshManager = new geometryMeshManager();
meshManager.CreateMeshes();
mapWidth = _mapWidth;
mapHeight = _mapHeight;
tileArray = new BaseTile[mapWidth, mapHeight];
for (int z = 0; z < mapHeight; z++)
{
for (int x = 0; x < mapWidth; x++)
{
int posX = x * tileSpacing;
int posZ = z * tileSpacing;
switch (networkedArray[x, z])
{
case TileType.Wall:
tileArray[x, z] = new Wall(meshManager.wallMesh.Name, mEngine.SceneMgr, new Vector3(posX, 0, posZ), tileSpacing);
break;
case TileType.Floor:
tileArray[x, z] = new Floor(meshManager.floorMesh.Name, mEngine.SceneMgr, new Vector3(posX, 0, posZ), tileSpacing);
break;
case TileType.Space:
tileArray[x, z] = new Space(mEngine.SceneMgr, new Vector3(posX, 0, posZ), tileSpacing);
break;
default:
break;
}
//mEngine.SceneMgr.RootSceneNode.RemoveChild(tileArray[x, z].tileNode);
tileArray[x, z].Node.SetVisible(false);
}
}
float geoSize = StaticGeoSize;
// Get the width and height our staticgeometry array needs to be.
StaticGeoX = (int)System.Math.Ceiling(mapWidth / geoSize);
StaticGeoZ = (int)System.Math.Ceiling(mapHeight / geoSize);
InitStaticgeometry();
for (int x = 0; x < mapWidth; x++)
{
for (int z = 0; z < mapHeight; z++)
{
// Get which piece of the staticGeometry array this tile belongs in (automatically
// worked out when the tile is created)
int GeoX = tileArray[x, z].GeoPosX;
int GeoZ = tileArray[x, z].GeoPosZ;
// Add it to the appropriate staticgeometry array.
staticGeometry[GeoX, GeoZ].AddSceneNode(tileArray[x, z].Node);
}
}
// Build all the staticgeometrys.
BuildAllgeometry();
mEngine.Update();
return true;
}
private void ParseNameArray(string[,] savedArray)
{
tileArray = new BaseTile[mapWidth, mapHeight];
for (int z = 0; z < mapHeight; z++)
{
for (int x = 0; x < mapWidth; x++)
{
int posX = x * tileSpacing;
int posZ = z * tileSpacing;
switch (savedArray[x, z])
{
case "wall":
tileArray[x, z] = new Wall(meshManager.wallMesh.Name, mEngine.SceneMgr, new Vector3(posX, 0, posZ), tileSpacing);
break;
case "floor":
tileArray[x, z] = new Floor(meshManager.floorMesh.Name, mEngine.SceneMgr, new Vector3(posX, 0, posZ), tileSpacing);
break;
default:
break;
}
//mEngine.SceneMgr.RootSceneNode.RemoveChild(tileArray[x, z].tileNode);
tileArray[x, z].Node.SetVisible(false);
}
}
}
#endregion
#region Geomap functions
// Initilizes the staticgeometry array, should only ever be called once when the map is being made
// for the first time.
private void InitStaticgeometry()
{
staticGeometry = new StaticGeometry[StaticGeoX, StaticGeoZ];
for (int i = 0; i < StaticGeoX; i++)
{
for (int j = 0; j < StaticGeoZ; j++)
{
staticGeometry[i, j] = mEngine.SceneMgr.CreateStaticGeometry("Mapgeometry" + "0" + i + "0" + j);
float halfway = tileSpacing * (StaticGeoSize / 2);
float originX = (i * (StaticGeoSize * tileSpacing));
float originY = 0f;
float originZ = ((j + 1)* (StaticGeoSize * tileSpacing));
staticGeometry[i, j].Origin = new Vector3(originX, originY, originZ);
staticGeometry[i, j].RegionDimensions = new Vector3(StaticGeoSize * tileSpacing, tileSpacing * 2, StaticGeoSize * tileSpacing);
staticGeometry[i, j].CastShadows = true;
}
}
}
// Builds all the geometry in the level, should only be called once when the map is being made
// for the first time.
private void BuildAllgeometry()
{
for (int i = 0; i < StaticGeoX; i++)
{
for (int j = 0; j < StaticGeoZ; j++)
{
staticGeometry[i, j].Build();
}
}
}
// Clears one part of the staticgeometry, repopulates it from the tile array and then rebuilds it.
// This is what should be used when a tile changes, passing in the location in the staticgeometry
// array that the tile lives in. (This is stored on the tile in .GeoPosX and .GeoPosZ)
public void RepopRebuildOnegeometry(int x, int y)
{
if (x > StaticGeoX || y > StaticGeoZ)
return;
staticGeometry[x, y].Reset();
for (int i = x * StaticGeoSize; i < (x + 1) * StaticGeoSize; i++)
{
for (int j = y * StaticGeoSize; j < (y + 1) * StaticGeoSize; j++)
{
staticGeometry[x, y].AddSceneNode(tileArray[i, j].Node);
}
}
staticGeometry[x, y].Build();
}
public void ClearOnegeometry(int x, int y)
{
if (x > StaticGeoX || y > StaticGeoZ)
return;
staticGeometry[x, y].Reset();
}
public Vector2 GetGeoArrayPositionFromWorldPosition(Vector3 pos)
{
Vector2 geoPos = Vector2.ZERO;
double camPosX = pos.x;
double camPosZ = pos.z;
camPosX /= (tileSpacing * StaticGeoSize);
camPosZ /= (tileSpacing * StaticGeoSize);
camPosX = System.Math.Floor(camPosX);
camPosZ = System.Math.Floor(camPosZ);
geoPos.x = (float)camPosX;
geoPos.y = (float)camPosZ;
return geoPos;
}
#endregion
#region Tile helper functions
// Returns the position of a tile in the tileArray from world coordinates
// Returns -1,-1 if an invalid position was passed in.
public Vector2 GetTileArrayPositionFromWorldPosition(float x, float z)
{
if (x < 0 || z < 0)
return new Vector2(-1, -1);
if (x > mapWidth * tileSpacing || z > mapWidth * tileSpacing)
return new Vector2(-1, -1);
int xPos = (int)System.Math.Floor(x / tileSpacing);
int zPos = (int)System.Math.Floor(z / tileSpacing);
return new Vector2(xPos, zPos);
}
private TileType GetObjectTypeFromWorldPosition(float x, float z)
{
Vector2 arrayPosition = GetTileArrayPositionFromWorldPosition(x, z);
if (arrayPosition.x < 0 || arrayPosition.y < 0)
{
return TileType.None;
}
else
{
return GetObjectTypeFromArrayPosition((int)arrayPosition.x, (int)arrayPosition.y);
}
}
private TileType GetObjectTypeFromWorldPosition(Vector3 pos)
{
Vector2 arrayPosition = GetTileArrayPositionFromWorldPosition(pos.x, pos.z);
if (arrayPosition.x < 0 || arrayPosition.y < 0)
{
return TileType.None;
}
else
{
return GetObjectTypeFromArrayPosition((int)arrayPosition.x, (int)arrayPosition.y);
}
}
private TileType GetObjectTypeFromArrayPosition(int x, int z)
{
if (x < 0 || z < 0 || x > mapWidth || z > mapHeight)
{
return TileType.None;
}
else
{
return tileArray[x, z].TileType;
}
}
// Changes a tile based on its array position (get from world
// coordinates using GetTileFromWorldPosition(int, int). Returns true if successful.
public bool ChangeTile(Vector2 arrayPosition, TileType newType)
{
int x = (int)arrayPosition.x;
int z = (int)arrayPosition.y;
if (x < 0 || z < 0)
return false;
if (x > mapWidth || z > mapWidth)
return false;
Vector3 pos = tileArray[x, z].Node.Position;
BaseTile tile = GenerateNewTile(newType, pos);
if (tile == null)
{
return false;
}
tileArray[x, z] = tile;
//mEngine.SceneMgr.RootSceneNode.RemoveChild(tileArray[x, z].tileNode);
tileArray[x, z].Node.SetVisible(false);
int xPos = tileArray[x, z].GeoPosX;
int yPos = tileArray[x, z].GeoPosZ;
RepopRebuildOnegeometry(xPos, yPos);
return true;
}
public bool ChangeTile(int x, int z, TileType newType)
{
Vector2 pos = new Vector2(x, z);
return ChangeTile(pos, newType);
}
public BaseTile GenerateNewTile(TileType type, Vector3 pos)
{
switch (type)
{
case TileType.Space:
Space space = new Space(mEngine.SceneMgr, pos, tileSpacing);
return space;
case TileType.Floor:
Floor floor = new Floor(meshManager.floorMesh.Name, mEngine.SceneMgr, pos, tileSpacing);
return floor;
case TileType.Wall:
Wall wall = new Wall(meshManager.wallMesh.Name, mEngine.SceneMgr, pos, tileSpacing);
return wall;
default:
return null;
}
}
#endregion
#region Quick collision checks
public bool CheckCollision(Vector3 pos)
{
TileType tile = GetObjectTypeFromWorldPosition(pos);
if (tile == TileType.None)
{
return false;
}
else if (tile == TileType.Wall && pos.y <= meshManager.GetWallHeight())
{
return true;
}
else if ((tile == TileType.Floor || tile == TileType.Space) && pos.y < 0)
{
return true;
}
else
{
return false;
}
}
public Vector3 GetPointAboveTileAt(Vector3 pos)
{
TileType tile = GetObjectTypeFromWorldPosition(pos);
if (tile == TileType.None)
{
return pos;
}
else if (tile == TileType.Wall)
{
return new Vector3(pos.x, 40f, pos.z);
}
else if ((tile == TileType.Floor || tile == TileType.Space) && pos.y < 0)
{
return new Vector3(pos.x, 0, pos.z);
}
else
{
return pos;
}
}
public float GetHeightAboveTileAt(Vector3 pos)
{
TileType tile = GetObjectTypeFromWorldPosition(pos);
if (tile == TileType.None)
{
return pos.y;
}
else if (tile == TileType.Wall)
{
return meshManager.GetWallHeight();
}
else if ((tile == TileType.Floor || tile == TileType.Space) && pos.y < 0)
{
return meshManager.GetFloorHeight();
}
else
{
return pos.y;
}
}
public TileType GetObjectTypeAt(Vector3 pos)
{
return GetObjectTypeFromWorldPosition(pos);
}
public bool IsFloorUnder(Vector3 pos)
{
if (GetObjectTypeFromWorldPosition(pos) == TileType.Floor)
{
return true;
}
else
{
return false;
}
}
public List<AxisAlignedBox> GetSurroundingAABB(Vector3 pos)
{
List<AxisAlignedBox> AABBList = new List<AxisAlignedBox>();
Vector2 tilePos = GetTileArrayPositionFromWorldPosition(pos.x, pos.z);
List<Vector2> cardinalList = new List<Vector2>();
cardinalList.Add(new Vector2(0, 0));
cardinalList.Add(new Vector2(0, 1));
cardinalList.Add(new Vector2(0, -1));
cardinalList.Add(new Vector2(1, 0));
cardinalList.Add(new Vector2(-1, 0));
cardinalList.Add(new Vector2(1, 1));
cardinalList.Add(new Vector2(-1, -1));
cardinalList.Add(new Vector2(-1, 1));
cardinalList.Add(new Vector2(1, -1));
foreach (Vector2 dir in cardinalList)
{
Vector2 checkPos = tilePos + dir;
if (GetObjectTypeFromArrayPosition((int)checkPos.x, (int)checkPos.y) == TileType.Wall)
{
AxisAlignedBox AABB = GetAABB(checkPos);
if (AABB != null)
{
AABBList.Add(AABB);
}
}
}
return AABBList;
}
public AxisAlignedBox GetAABB(Vector2 tilePos)
{
if (tilePos.x < 0 || tilePos.x > mapWidth || tilePos.y < 0 || tilePos.y > mapHeight)
{
return null;
}
return tileArray[(int)tilePos.x, (int)tilePos.y].Node._getWorldAABB();
}
#endregion
#region Shutdown
public void Shutdown()
{
for (int i = 0; i < StaticGeoX; i++)
{
for (int j = 0; j < StaticGeoZ; j++)
{
staticGeometry[i, j].Reset();
}
}
mEngine.SceneMgr.DestroyAllStaticGeometry();
mEngine.SceneMgr.DestroyAllEntities();
for (int x = 0; x < mapWidth; x++)
{
for (int z = 0; z < mapHeight; z++)
{
mEngine.SceneMgr.DestroySceneNode(tileArray[x, z].Node.Name);
}
}
tileArray = null;
meshManager = null;
boundingBoxArray = null;
}
#endregion
}
}

View File

@@ -0,0 +1,88 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using Mogre;
using SS3D.HelperClasses;
using SS3D_shared;
namespace SS3D.Modules.Map
{
public class MapSaver
{
private Map map;
private OgreManager mEngine;
private int tileSpacing;
public string[,] nameArray;
public int mapWidth;
public int mapHeight;
public MapSaver(Map _map, OgreManager _mEngine)
{
map = _map;
mEngine = _mEngine;
tileSpacing = map.tileSpacing;
}
public bool Save(string filename)
{
if (filename == "")
{
return false;
}
FileStream fs = new FileStream(filename, FileMode.Create);
StreamWriter sw = new StreamWriter(fs);
sw.WriteLine(map.mapWidth);
sw.WriteLine(map.mapHeight);
for (int y = 0; y < map.mapHeight; y++)
{
for (int x = 0; x < map.mapWidth; x++)
{
sw.WriteLine(map.tileArray[x,y].name);
}
}
sw.Close();
fs.Close();
return true;
}
public bool Load(string filename)
{
if(!File.Exists(filename))
{
return false;
}
FileStream fs = new FileStream(filename, FileMode.Open);
StreamReader sr = new StreamReader(fs);
mapWidth = int.Parse(sr.ReadLine());
mapHeight = int.Parse(sr.ReadLine());
nameArray = new string[mapWidth, mapHeight];
for (int y = 0; y < mapHeight; y++)
{
for (int x = 0; x < mapWidth; x++)
{
nameArray[x, y] = sr.ReadLine();
}
}
sr.Close();
fs.Close();
return true;
}
}
}

View File

@@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Miyagi;
using Miyagi.Common;
using Miyagi.Common.Resources;
namespace SS3D.Modules
{
public sealed class MiyagiResources
{
static MiyagiResources()
{
}
MiyagiResources()
{
}
public static MiyagiResources Singleton
{
get
{
return singleton;
}
}
static readonly MiyagiResources singleton = new MiyagiResources();
public Dictionary<string, Skin> Skins;
public Dictionary<string, Font> Fonts;
public MiyagiSystem mMiyagiSystem
{
get;
private set;
}
public void Initialize(MiyagiSystem mSystem)
{
mMiyagiSystem = mSystem;
}
}
}

View File

@@ -0,0 +1,245 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Lidgren.Network;
using SS3D_shared;
using Mogre;
namespace SS3D.Modules.Mobs
{
public class MobManager
{
private Map.Map map;
private OgreManager mEngine;
private Network.NetworkManager networkManager;
private Dictionary<ushort, Mob> mobDict; // MobID, Mob
private string mobAssemblyName;
private DateTime lastMobUpdate = DateTime.Now;
private DateTime lastSentUpdate = DateTime.Now;
private DateTime lastAnimUpdate = DateTime.Now;
private double mobUpdateTime = 10;
private double serverUpdateTime = 50;
private ushort myMobID = 0;
public MobManager(OgreManager _mEngine, Map.Map _map, Network.NetworkManager _networkManager)
{
mEngine = _mEngine;
map = _map;
networkManager = _networkManager;
mobDict = new Dictionary<ushort, Mob>();
mobAssemblyName = typeof(SS3D_shared.Item).Assembly.ToString();
}
public void HandleNetworkMessage(NetIncomingMessage message)
{
MobMessage messageType = (MobMessage)message.ReadByte();
switch (messageType)
{
case MobMessage.CreateMob:
HandleCreateMob(message);
break;
case MobMessage.InterpolationPacket:
HandleInterpolationPacket(message);
break;
default:
break;
}
}
public void Update()
{
TimeSpan updateSpan = DateTime.Now - lastMobUpdate;
if (updateSpan.TotalMilliseconds > mobUpdateTime)
{
foreach (ushort mobID in mobDict.Keys)
{
if (mobID == myMobID)
{
if (mobDict[mobID].interpolationPacket.Count > 0)
{
UpdateMyMobPosition();
}
}
else if (mobDict[mobID].interpolationPacket.Count > 0)
{
UpdateMobPosition(mobID);
}
}
lastMobUpdate = DateTime.Now;
}
if (mobDict.ContainsKey(myMobID))
{
TimeSpan myLastSpan = DateTime.Now - lastSentUpdate;
if (myLastSpan.TotalMilliseconds > serverUpdateTime)
{
NetOutgoingMessage message = networkManager.netClient.CreateMessage();
message.Write((byte)NetMessage.MobMessage);
message.Write((byte)MobMessage.InterpolationPacket);
message.Write((ushort)myMobID);
message.Write((float)mobDict[myMobID].Node.Position.x);
message.Write((float)mobDict[myMobID].Node.Position.y);
message.Write((float)mobDict[myMobID].Node.Position.z);
message.Write((float)mobDict[myMobID].Node.Orientation.w);
message.Write((float)mobDict[myMobID].Node.Orientation.y);
networkManager.SendMessage(message, NetDeliveryMethod.ReliableOrdered);
lastSentUpdate = DateTime.Now;
}
}
TimeSpan animTime = lastAnimUpdate - DateTime.Now;
foreach (ushort mobID in mobDict.Keys)
{
mobDict[mobID].animState.AddTime((float)animTime.TotalMilliseconds / 1000f);
}
lastAnimUpdate = DateTime.Now;
}
// This is horrible I know, it will be changed!
public void MoveMe(int i)
{
mobDict[myMobID].animState = mobDict[myMobID].Entity.GetAnimationState("Walk");
mobDict[myMobID].animState.Enabled = true;
mobDict[myMobID].animState.Loop = true;
Mogre.Vector3 lastPosition = mobDict[myMobID].Node.Position;
switch (i)
{
case 1:
mobDict[myMobID].Node.Translate(new Vector3(1, 0, 0), Node.TransformSpace.TS_LOCAL);
break;
case 2:
mobDict[myMobID].Node.Rotate(Mogre.Vector3.UNIT_Y, Mogre.Math.DegreesToRadians(-2));
break;
case 3:
mobDict[myMobID].Node.Rotate(Mogre.Vector3.UNIT_Y, Mogre.Math.DegreesToRadians(2));
break;
case 4:
mobDict[myMobID].Node.Translate(new Vector3(-1, 0, 0), Node.TransformSpace.TS_LOCAL);
break;
default:
break;
}
foreach (AxisAlignedBox box in map.GetSurroundingAABB(mobDict[myMobID].Node.Position))
{
if (mobDict[myMobID].Entity.GetWorldBoundingBox().Intersects(box))
{
mobDict[myMobID].Node.Position = lastPosition;
return;
}
}
}
public void Shutdown()
{
foreach (Mob mob in mobDict.Values)
{
//mEngine.SceneMgr.DestroyEntity(mob.Entity);
mEngine.SceneMgr.DestroySceneNode(mob.Node);
}
mobDict = null;
}
#region Mob moving
private void HandleInterpolationPacket(NetIncomingMessage msg)
{
ushort mobID = msg.ReadUInt16();
float x = msg.ReadFloat();
float y = msg.ReadFloat();
float z = msg.ReadFloat();
float rotW = msg.ReadFloat();
float rotY = msg.ReadFloat();
SS3D_shared.HelperClasses.InterpolationPacket intPacket = new SS3D_shared.HelperClasses.InterpolationPacket(x, y, z, rotW, rotY, 0);
if(!mobDict.ContainsKey(mobID))
{
return;
}
mobDict[mobID].interpolationPacket.Add(intPacket);
if (mobDict[mobID].interpolationPacket.Count > 10)
{
mobDict[mobID].interpolationPacket.RemoveAt(0);
}
}
private void UpdateMobPosition(ushort mobID)
{
Vector3 difference;
float rotW, rotY;
// Removing this for now as I fucked it up somehow - need to read more.
/*if (mobDict[mobID].interpolationPacket.Count < 10)
{*/
difference = mobDict[mobID].interpolationPacket[0].position - mobDict[mobID].Node.Position;
rotW = mobDict[mobID].interpolationPacket[0].rotW;
rotY = mobDict[mobID].interpolationPacket[0].rotY;
/*}
else
{
difference = mobDict[mobID].interpolationPacket[9].position- mobDict[mobID].interpolationPacket[8].position;
}*/
if (System.Math.Round(difference.Length) != 0)
{
mobDict[mobID].animState = mobDict[mobID].Entity.GetAnimationState("Walk");
mobDict[mobID].animState.Loop = true;
mobDict[mobID].animState.Enabled = true;
}
else
{
mobDict[mobID].animState = mobDict[mobID].Entity.GetAnimationState("Idle");
mobDict[mobID].animState.Loop = true;
mobDict[mobID].animState.Enabled = true;
}
difference /= (float)(serverUpdateTime / mobUpdateTime);
mobDict[mobID].Node.Position += difference;
mobDict[mobID].Node.SetOrientation(rotW, 0, rotY, 0);
}
private void UpdateMyMobPosition()
{
}
#endregion
#region Mob creation
private void HandleCreateMob(NetIncomingMessage msg)
{
string mobName = msg.ReadString();
ushort mobID = msg.ReadUInt16();
bool myMob = msg.ReadBoolean();
float x = msg.ReadFloat();
float y = msg.ReadFloat();
float z = msg.ReadFloat();
float rotW = msg.ReadFloat();
float rotY = msg.ReadFloat();
Player mob = new Player(mEngine.SceneMgr, new Vector3(x, y, z), mobID);
mobDict[mobID] = mob;
mob.Node.SetOrientation(rotW, 0, rotY, 0);
if (myMob)
{
myMobID = mobID;
mob.Node.AttachObject(mEngine.Camera);
mEngine.Camera.Position = new Vector3(-160, 160, 0);
mEngine.Camera.SetAutoTracking(true, mob.Node);
}
}
#endregion
}
}

View File

@@ -0,0 +1,241 @@
using System;
using System.Text;
using SS3D.Modules;
using SS3D.States;
using System.Collections.Generic;
using SS3D_shared;
using Lidgren.Network;
namespace SS3D.Modules.Network
{
public delegate void NetworkMsgHandler(NetworkManager netMgr, NetIncomingMessage msg);
public delegate void NetworkStateHandler(NetworkManager netMgr);
public class NetworkManager
{
private OgreManager mEngine;
private StateManager mStateMgr;
private TileType[,] tileArray;
private Map.Map mMap;
private GameType serverGameType;
public bool mapRecieved = false;
private int mapWidth;
private int mapHeight;
private string serverName = "SS3D Server";
public bool isConnected
{
get;
private set;
}
public NetClient netClient
{
get;
private set;
}
private NetPeerConfiguration netConfig = new NetPeerConfiguration("SS3D_NetTag");
public event NetworkMsgHandler MessageArrived; //Called when we recieve a new message.
protected virtual void OnMessageArrived(NetIncomingMessage msg)
{
if (MessageArrived != null) MessageArrived(this, msg);
}
public event NetworkStateHandler Connected; //Called when we connect to a server.
protected virtual void OnConnected()
{
if (Connected != null) Connected(this);
}
public event NetworkStateHandler Disconnected; //Called when we Disconnect from a server.
protected virtual void OnDisconnected()
{
if (Disconnected != null) Disconnected(this);
}
public NetworkManager(OgreManager engine, StateManager statem)
{
mEngine = engine;
mStateMgr = statem;
isConnected = false;
netClient = new NetClient(netConfig);
netClient.Start();
}
public void SetMap(Map.Map _map)
{
mMap = _map;
}
public void ConnectTo(string host)
{
netClient.Connect(host,1212);
}
public void Disconnect()
{
Restart();
}
public void Restart()
{
netClient.Shutdown("Leaving");
netClient = new NetClient(netConfig);
netClient.Start();
}
public void UpdateNetwork()
{
if (!isConnected && netClient.ServerConnection != null)
{
OnConnected();
isConnected = true;
}
else if (isConnected && netClient.ServerConnection == null)
{
OnDisconnected();
isConnected = false;
}
if (isConnected)
{
NetIncomingMessage msg;
while ((msg = netClient.ReadMessage()) != null)
{
OnMessageArrived(msg);
//switch (msg.MessageType)
//{
// case NetIncomingMessageType.Data:
// NetMessage messageType = (NetMessage)msg.ReadByte();
// switch (messageType)
// {
// case NetMessage.SendMap:
// RecieveMap(msg);
// break;
// case NetMessage.GameType:
// SetGameType(msg);
// break;
// }
// break;
// default:
// break;
//}
netClient.Recycle(msg);
}
}
}
public void ShutDown()
{
netClient.Shutdown("Quitting");
}
public void SetGameType(NetIncomingMessage msg)
{
serverGameType = (GameType)msg.ReadByte();
}
public void RequestMap()
{
NetOutgoingMessage message = netClient.CreateMessage();
message.Write((byte)NetMessage.SendMap);
netClient.SendMessage(message, NetDeliveryMethod.ReliableUnordered);
}
public void RecieveMap(NetIncomingMessage msg)
{
mapWidth = msg.ReadInt32();
mapHeight = msg.ReadInt32();
tileArray = new TileType[mapWidth, mapHeight];
for (int x = 0; x < mapWidth; x++)
{
for (int z = 0; z < mapHeight; z++)
{
tileArray[x, z] = (TileType)msg.ReadByte();
}
}
mapRecieved = true;
}
public void SendChangeTile(int x, int z, TileType newTile)
{
NetOutgoingMessage netMessage = netClient.CreateMessage();
netMessage.Write((byte)NetMessage.ChangeTile);
netMessage.Write(x);
netMessage.Write(z);
netMessage.Write((byte)newTile);
netClient.SendMessage(netMessage, NetDeliveryMethod.Unreliable);
}
public NetOutgoingMessage GetMessage()
{
return netClient.CreateMessage();
}
public void SendClientName(string name)
{
NetOutgoingMessage message = netClient.CreateMessage();
message.Write((byte)NetMessage.ClientName);
message.Write(name);
netClient.SendMessage(message, NetDeliveryMethod.Unreliable);
}
public void SendMessage(NetOutgoingMessage message, NetDeliveryMethod deliveryMethod)
{
if (message != null)
{
netClient.SendMessage(message, deliveryMethod);
}
}
public NetIncomingMessage GetNetworkUpdate()
{
NetIncomingMessage msg;
if((msg = netClient.ReadMessage()) != null)
{
return msg;
}
return null;
}
public TileType[,] GetTileArray()
{
return tileArray;
}
public int GetMapHeight()
{
return mapHeight;
}
public int GetMapWidth()
{
return mapWidth;
}
public string GetServerName()
{
return serverName;
}
public string GetServerAddress()
{
return (netClient.ServerConnection.RemoteEndpoint.Address.ToString() + ":" + netClient.Port.ToString());
}
public GameType GetGameType()
{
return serverGameType;
}
}
}

View File

@@ -0,0 +1,40 @@
using System;
namespace SS3D.Modules
{
/************************************************************************/
/* event arguments for ogre events (device lost or restored) */
/************************************************************************/
public class OgreEventArgs : EventArgs
{
private int mWidth;
private int mHeight;
// get width of screen after device restored, 0 if device lost
public int Width
{
get { return mWidth; }
}
// get height of screen after device restored, 0 if device lost
public int Height
{
get { return mHeight; }
}
public OgreEventArgs() //constructor
: this( 0, 0 )
{
}
public OgreEventArgs( int _width, int _height ) // constructor
{
// store width and height
mWidth = _width;
mHeight = _height;
}
}
}

View File

@@ -0,0 +1,339 @@
using System;
using System.Collections.Generic;
using System.IO;
using Lidgren.Network;
using Mogre;
using Miyagi;
using Miyagi.Common;
using SS3D.Modules.Network;
namespace SS3D.Modules
{
/************************************************************************/
/* ogre manager */
/************************************************************************/
public class OgreManager
{
private Root mRoot;
private RenderWindow mWindow;
private SceneManager mSceneMgr;
private Camera mCamera;
private Viewport mViewport;
private bool mRenderingActive;
public NetworkManager mNetworkMgr;
public MiyagiSystem mMiyagiSystem;
public StateManager mStateMgr;
private double scalarX;
private double scalarY;
public double ScalarX
{
private set { scalarX = value; }
get { return scalarX; }
}
public double ScalarY
{
private set { scalarY = value; }
get { return scalarY; }
}
// flag is true if rendering is currently active
public bool RenderingActive
{
get { return mRenderingActive; }
}
public RenderWindow Window
{
get { return mWindow; }
}
public Root Root
{
get { return mRoot; }
}
public SceneManager SceneMgr
{
get { return mSceneMgr; }
}
public Camera Camera
{
get { return mCamera; }
}
// events raised when direct 3D device is lost or restored
public event EventHandler<OgreEventArgs> DeviceLost;
public event EventHandler<OgreEventArgs> DeviceRestored;
internal OgreManager() //constructor
{
mRoot = null;
mWindow = null;
mSceneMgr = null;
mCamera = null;
mViewport = null;
mRenderingActive = false;
mMiyagiSystem = null;
}
#region Startup, Shutdown, Update
internal bool Startup(Configuration config)
{
// check if already initialized
if (mRoot != null)
return false;
// create ogre root
mRoot = new Root("plugins.cfg", "settings.cfg", "mogre.log");
//Not sure if we should load all this stuff from files. Cheaters?
//See http://www.ogre3d.org/tikiwiki/Mogre+Basic+Tutorial+5 about hardcoding it.
// set directx render system
RenderSystem renderSys = mRoot.GetRenderSystemByName("Direct3D9 Rendering Subsystem");
mRoot.RenderSystem = renderSys;
// register event to get notified when application lost or regained focus
mRoot.RenderSystem.EventOccurred += OnRenderSystemEventOccurred;
// initialize engine
mRoot.Initialise(false);
// optional parameters
NameValuePairList parm = new NameValuePairList();
parm["vsync"] = config.VSync.ToString();
parm["FSAA"] = config.FSAA.ToString();
// create window
mWindow = mRoot.CreateRenderWindow("SpaceStation3D", (uint)config.DisplayWidth, (uint)config.DisplayHeight, config.Fullscreen, parm);
// Assign scale vars - Base Resolution = 1024 x 768
scalarX = (double)config.DisplayWidth / (double)1024;
scalarY = (double)config.DisplayHeight / (double)768;
// create scene manager
mSceneMgr = mRoot.CreateSceneManager(SceneType.ST_GENERIC, "DefaultSceneManager");
// Add options & config entries for stuff below.
MaterialManager.Singleton.SetDefaultTextureFiltering(TextureFilterOptions.TFO_ANISOTROPIC);
MaterialManager.Singleton.DefaultAnisotropy = 8;
TextureManager.Singleton.DefaultNumMipmaps = 8;
// create default camera
mCamera = mSceneMgr.CreateCamera("DefaultCamera");
mCamera.AutoAspectRatio = true;
mCamera.NearClipDistance = 1.0f;
mCamera.FarClipDistance = 1500.0f;
mCamera.Position = new Mogre.Vector3(0, 300,0);
mCamera.LookAt(Mogre.Vector3.ZERO);
// create default viewport
mViewport = mWindow.AddViewport(mCamera);
// set rendering active flag
mRenderingActive = true;
// OK
return true;
}
internal void Shutdown()
{
// shutdown ogre root
if (mRoot != null)
{
// deregister event to get notified when application lost or regained focus
mRoot.RenderSystem.EventOccurred -= OnRenderSystemEventOccurred;
// shutdown ogre
mRoot.Dispose();
}
mRoot = null;
// forget other references to ogre systems
mWindow = null;
mSceneMgr = null;
mCamera = null;
mViewport = null;
mRenderingActive = false;
}
internal void Update()
{
// check if ogre manager is initialized
if (mRoot == null)
return;
// process windows event queue (only if no external window is used)
WindowEventUtilities.MessagePump();
// render next frame
if (mRenderingActive)
mRoot.RenderOneFrame();
}
#endregion
// handle device lost and device restored events
private void OnRenderSystemEventOccurred( string eventName, Const_NameValuePairList parameters )
{
EventHandler<OgreEventArgs> evt = null;
OgreEventArgs args;
// check which event occured
switch( eventName )
{
// direct 3D device lost
case "DeviceLost":
// don't set mRenderingActive to false here, because ogre will try to restore the
// device in the RenderOneFrame function and mRenderingActive needs to be set to true
// for this function to be called
// event to raise is device lost event
evt = DeviceLost;
// on device lost, create empty ogre event args
args = new OgreEventArgs();
break;
// direct 3D device restored
case "DeviceRestored":
uint width;
uint height;
uint depth;
// event to raise is device restored event
evt = DeviceRestored;
// get metrics for the render window size
mWindow.GetMetrics( out width, out height, out depth );
// on device restored, create ogre event args with new render window size
args = new OgreEventArgs( (int) width, (int) height );
break;
default:
return;
}
// raise event with provided event args
if( evt != null )
evt( this, args );
}
// create a simple object just consisting of a scenenode with a mesh
internal SceneNode CreateSimpleObject( string _name, string _mesh )
{
// if scene manager already has an object with the requested name, fail to create it again
if( mSceneMgr.HasEntity( _name ) || mSceneMgr.HasSceneNode( _name ) )
return null;
// create entity and scenenode for the object
Entity entity;
try
{
// try to create entity from mesh
entity = mSceneMgr.CreateEntity( _name, _mesh );
}
catch
{
// failed to create entity
return null;
}
// add entity to scenenode
SceneNode node = mSceneMgr.CreateSceneNode( _name );
// connect entity to the scenenode
node.AttachObject( entity );
// return the created object
return node;
}
// destroy an object
internal void DestroyObject( SceneNode _node )
{
// check if object has a parent node...
if( _node.Parent != null )
{
// ...if so, remove it from its parent node first
_node.Parent.RemoveChild( _node );
}
// first remove all child nodes (they are not destroyed here !)
_node.RemoveAllChildren();
// create a list of references to attached objects
List<MovableObject> objList = new List<MovableObject>();
// get number of attached objects
ushort count = _node.NumAttachedObjects();
// get all attached objects references
for( ushort i = 0; i < count; ++i )
objList.Add( _node.GetAttachedObject( i ) );
// detach all objects from node
_node.DetachAllObjects();
// destroy all previously attached objects
foreach( MovableObject obj in objList )
mSceneMgr.DestroyMovableObject( obj );
// destroy scene node
mSceneMgr.DestroySceneNode( _node );
}
// add an object to the scene
internal void AddObjectToScene( SceneNode _node )
{
// check if object is already has a parent
if( _node.Parent != null )
{
// check if object is in scene already, then we are done
if( _node.Parent == mSceneMgr.RootSceneNode )
return;
// otherwise remove the object from its current parent
_node.Parent.RemoveChild( _node );
}
// add object to scene
mSceneMgr.RootSceneNode.AddChild( _node );
}
// add an object to another object as child
internal void AddObjectToObject( SceneNode _node, SceneNode _newParent )
{
// check if object is already has a parent
if( _node.Parent != null )
{
// check if object is in scene already, then we are done
if( _node.Parent == _newParent )
return;
// otherwise remove the object from its current parent
_node.Parent.RemoveChild( _node );
}
// add object to scene
_newParent.AddChild( _node );
}
// remove object from scene
internal void RemoveObjectFromScene( SceneNode _node )
{
// if object is attached to a node
if( _node.Parent != null )
{
// remove object from its parent
_node.Parent.RemoveChild( _node );
}
}
}
}

View File

@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
namespace SS3D.Modules
{
/************************************************************************/
/* base class for program states */
/************************************************************************/
public abstract class State
{
public State() //constructor
{
}
public abstract bool Startup( StateManager _mgr );
public abstract void Shutdown();
public abstract void Update( long _frameTime );
public abstract void UpdateInput(Mogre.FrameEvent evt, MOIS.Keyboard keyState, MOIS.Mouse mouseState);
public abstract void KeyDown(MOIS.KeyEvent keyState);
public abstract void KeyUp(MOIS.KeyEvent keyState);
public abstract void MouseUp(MOIS.MouseEvent mouseState, MOIS.MouseButtonID button);
public abstract void MouseDown(MOIS.MouseEvent mouseState, MOIS.MouseButtonID button);
public abstract void MouseMove(MOIS.MouseEvent mouseState);
}
}

View File

@@ -0,0 +1,185 @@
using System;
using System.Reflection;
using Mogre;
using Miyagi;
using Lidgren.Network;
using SS3D.Modules.Network;
namespace SS3D.Modules
{
/************************************************************************/
/* state manager for program states */
/************************************************************************/
public class StateManager
{
private OgreManager mEngine;
public State mCurrentState
{
private set;
get;
}
private Type mNewState;
public OgreManager Engine
{
get { return mEngine; }
}
#region Startup , Shutdown , Constructor
public StateManager(OgreManager _engine)
{
// constructor
mEngine = _engine;
mCurrentState = null;
mNewState = null;
}
public bool Startup(Type _firstState)
{
// start up and initialize the first state
// can't start up the state manager again if it's already running
if (mCurrentState != null || mNewState != null)
return false;
// initialize with first state
if (!RequestStateChange(_firstState))
return false;
// OK
return true;
}
public void Shutdown()
{
// if a state is active, shut down the state to clean up
if (mCurrentState != null)
SwitchToNewState(null);
// make sure any pending state change request is reset
mNewState = null;
}
#endregion
#region Input
public void UpdateInput(FrameEvent evt, MOIS.Keyboard keyState, MOIS.Mouse mouseState)
{
// if a state is active, call the states input update.
if (mCurrentState != null)
mCurrentState.UpdateInput(evt, keyState, mouseState);
}
public void KeyDown(MOIS.KeyEvent keyState)
{
// if a state is active, call the states keydown method.
if (mCurrentState != null)
mCurrentState.KeyDown(keyState);
}
public void KeyUp(MOIS.KeyEvent keyState)
{
// if a state is active, call the states keyup method.
if (mCurrentState != null)
mCurrentState.KeyUp(keyState);
}
public void MouseUp(MOIS.MouseEvent mouseState, MOIS.MouseButtonID button)
{
// if a state is active, call the states mouseup method.
if (mCurrentState != null)
mCurrentState.MouseUp(mouseState, button);
}
public void MouseDown(MOIS.MouseEvent mouseState, MOIS.MouseButtonID button)
{
// if a state is active, call the states mousedown method.
if (mCurrentState != null)
mCurrentState.MouseDown(mouseState, button);
}
public void MouseMove(MOIS.MouseEvent mouseState)
{
// if a state is active, call the states mousemove method.
if (mCurrentState != null)
mCurrentState.MouseMove(mouseState);
}
#endregion
#region Updates & Statechanges
public void Update(long _frameTime)
{
// check if a state change was requested
if (mNewState != null)
{
State newState = null;
// use reflection to get new state class default constructor
ConstructorInfo constructor = mNewState.GetConstructor(Type.EmptyTypes);
// try to create an object from the requested state class
if (constructor != null)
newState = (State)constructor.Invoke(null);
// remove all current ui elements
// switch to the new state if an object of the requested state class could be created
if (newState != null)
SwitchToNewState(newState);
// reset state change request until next state change is requested
mNewState = null;
}
// if a state is active, update the active state
if (mCurrentState != null)
{
mCurrentState.Update(_frameTime);
}
}
public bool RequestStateChange(Type _newState)
{
// set next state that should be switched to, returns false if invalid
// new state class must be derived from base class "State"
if (_newState == null || !_newState.IsSubclassOf(typeof(State)))
return false;
// don't change the state if the requested state class matches the current state
if (mCurrentState != null && mCurrentState.GetType() == _newState)
return false;
// store type of new state class to request a state change
mNewState = _newState;
// OK
return true;
}
//////////////////////////////////////////////////////////////////////////
// internal functions ////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
private void SwitchToNewState(State _newState)
{
//change from one state to another state
//if a state is active, shut it down
if (mCurrentState != null)
mCurrentState.Shutdown();
// switch to the new state, might be null if no new state should be activated
mCurrentState = _newState;
// if a state is active, start it up
if (mCurrentState != null)
mCurrentState.Startup(this);
}
#endregion
}
}

View File

@@ -0,0 +1,244 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Miyagi;
using Miyagi.UI;
using Miyagi.UI.Controls;
using Miyagi.Common;
using Miyagi.Common.Data;
using Miyagi.Common.Resources;
using Miyagi.Common.Events;
using Miyagi.TwoD;
namespace SS3D.Modules.UI
{
class Chatbox
{
private MiyagiResources mMiyagiRes;
public Panel chatPanel
{
private set;
get;
}
public TextBox chatTextbox
{
private set;
get;
}
public GUI chatGUI
{
private set;
get;
}
private List<Label> entries = new List<Label>();
private int currentYpos = 0;
private readonly int maxLines = 20;
public Chatbox(string name)
{
mMiyagiRes = MiyagiResources.Singleton;
chatGUI = new GUI(name);
this.chatPanel = new Panel(name+"Panel")
{
TabStop = false,
TabIndex = 0,
Size = new Size(400, 150),
Location = new Point(0, 0),
MinSize = new Size(100, 100),
AlwaysOnTop = false,
Movable = true,
ResizeThreshold = new Thickness(3),
Padding = new Thickness(2, 2, 2, 2),
BorderStyle =
{
Thickness = new Thickness(3, 3, 3, 3)
},
HScrollBarStyle =
{
ShowButtons = false,
Extent = 16,
BorderStyle =
{
Thickness = new Thickness(2, 2, 2, 2)
},
ThumbStyle =
{
BorderStyle =
{
Thickness = new Thickness(2, 2, 2, 2)
}
}
},
VScrollBarStyle =
{
ShowButtons = false,
Extent = 16,
BorderStyle =
{
Thickness = new Thickness(2, 2, 2, 2)
},
ThumbStyle =
{
BorderStyle =
{
Thickness = new Thickness(2, 2, 2, 2)
}
}
},
TextureFiltering = Miyagi.Common.TextureFiltering.Anisotropic,
Skin = MiyagiResources.Singleton.Skins["ConsolePanelSkin"]
};
chatPanel.SizeChanged += new EventHandler(chatPanel_SizeChanged);
chatPanel.LocationChanged += new EventHandler<ChangedValueEventArgs<Point>>(chatPanel_LocationChanged);
this.chatTextbox = new TextBox("ChatTextbox")
{
Size = new Size(400, 30),
Location = new Point(0, 150),
Padding = new Thickness(5, 3, 5, 3),
AlwaysOnTop = false,
DefocusOnSubmit = false,
BorderStyle =
{
Thickness = new Thickness(3, 3, 3, 3)
},
TextStyle =
{
Offset = new Point(0, 3),
Alignment = Alignment.MiddleLeft,
ForegroundColour = Colours.White
},
TextBoxStyle =
{
CaretStyle =
{
Size = new Size(2, 16),
Colour = Colours.White
}
},
Skin = MiyagiResources.Singleton.Skins["ConsoleTextBoxSkin"],
ClearTextOnSubmit = true
};
chatTextbox.Submit += new EventHandler<ValueEventArgs<string>>(chatTextbox_Submit);
chatGUI.Controls.Add(chatPanel);
chatGUI.Controls.Add(chatTextbox);
chatGUI.ZOrder = 10;
}
void chatTextbox_Submit(object sender, ValueEventArgs<string> e)
{
AddLine(e.Data);
}
public void AddLine(string text)
{
var label = new Label
{
Location = new Point(0, currentYpos),
Text = text,
AutoSize = true,
TextStyle =
{
ForegroundColour = Colours.LightGrey
}
};
label.SuccessfulHitTest += (s, e) => e.Cancel = true;
this.chatPanel.Controls.Add(label);
if (!chatGUI.Visible) //Fuck miyagi. If the ui is hidden the size of it is 0,0. So here. Ugly hack.
{
chatGUI.Visible = true;
this.currentYpos += label.Size.Height;
chatGUI.Visible = false;
}
else this.currentYpos += label.Size.Height;
this.entries.Add(label);
if (entries.Count > maxLines) Trim();
chatPanel.ScrollToBottom();
}
public void AddLine(string text, Colour colour)
{
var label = new Label
{
Location = new Point(0, currentYpos),
Text = text,
AutoSize = true,
TextStyle =
{
ForegroundColour = colour
}
};
label.SuccessfulHitTest += (s, e) => e.Cancel = true;
this.chatPanel.Controls.Add(label);
if (!chatGUI.Visible) //Fuck miyagi. If the ui is hidden the size of it is 0,0. So here. Ugly hack.
{
chatGUI.Visible = true;
this.currentYpos += label.Size.Height;
chatGUI.Visible = false;
}
else this.currentYpos += label.Size.Height;
this.entries.Add(label);
if (entries.Count > maxLines) Trim();
chatPanel.ScrollToBottom();
}
void Trim()
{
if (entries.Count < 2) return; //This should never happen. Just make sure maxlines is > 2.
entries[1].Location = new Point(0, 0);
Label toDelete = entries[0];
entries.RemoveAt(0); //Remove the oldest element.
currentYpos -= toDelete.Size.Height;
toDelete.Dispose();
for (int i = 0; i < entries.Count; i++) //Update the positions of the other elements.
{
entries[i].Location = (i != 0) ? new Point(0, entries[i - 1].Location.Y + entries[i - 1].Size.Height) : new Point(0, 0);
}
}
void Clear()
{
foreach (Label lbl in entries)
{
lbl.Dispose();
}
currentYpos = 0;
chatPanel.ScrollToTop();
}
void chatPanel_LocationChanged(object sender, ChangedValueEventArgs<Point> e)
{
if (chatTextbox != null)
{
Point newLoc = new Point(chatPanel.Location.X, chatPanel.Location.Y + chatPanel.Size.Height);
chatTextbox.Location = newLoc;
}
}
void chatPanel_SizeChanged(object sender, EventArgs e)
{
if (chatTextbox != null)
{
Size newSize = new Size(chatPanel.Size.Width, 30);
Point newLoc = new Point(chatPanel.Location.X, chatPanel.Location.Y + chatPanel.Size.Height);
chatTextbox.Size = newSize;
chatTextbox.Location = newLoc;
}
}
}
}

View File

@@ -0,0 +1,320 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Miyagi;
using Miyagi.UI;
using Miyagi.UI.Controls;
using Miyagi.Common;
using Miyagi.Common.Data;
using Miyagi.Common.Resources;
using Miyagi.Common.Events;
using Miyagi.TwoD;
namespace SS3D.Modules.UI
{
class GameConsole
{
static readonly GameConsole singleton = new GameConsole();
private MiyagiResources mMiyagiRes;
private OgreManager mEngine;
private Panel consolePanel;
private TextBox consoleTextbox;
private GUI consoleGUI;
private List<Label> entries = new List<Label>();
private List<String> log = new List<string>();
private int currentYpos = 0;
private readonly int maxLines = 75;
private bool visible = false;
public bool Visible
{
get
{
return visible;
}
set
{
if (consoleGUI != null)
{
//foreach (Label lbl in entries) lbl.Visible = value;
//consoleGUI.Fade(value ? 0 : 0.95f, value ? 0.95f : 0, 100);
consoleGUI.Visible = value;
consolePanel.Enabled = consoleTextbox.Enabled = value;
visible = value;
consoleTextbox.Focused = value;
consoleGUI.ZOrder = 100;
consoleGUI.EnsureZOrder();
}
}
}
static GameConsole()
{
}
GameConsole()
{
}
public static GameConsole Singleton
{
get
{
return singleton;
}
}
public void Initialize(OgreManager engine)
{
mMiyagiRes = MiyagiResources.Singleton;
mEngine = engine;
if(consoleGUI != null)
{
Visible = true;
return;
}
consoleGUI = new GUI("Console");
this.consolePanel = new Panel("ConsolePanel")
{
TabStop = false,
TabIndex = 0,
Size = new Size(800, 300),
Location = new Point(0, 0),
MinSize = new Size(100,100),
AlwaysOnTop = false,
Movable = true,
ResizeThreshold = new Thickness(3),
Padding = new Thickness(2,2,2,2),
BorderStyle =
{
Thickness = new Thickness(3, 3, 3, 3)
},
HScrollBarStyle =
{
ShowButtons = false,
Extent = 16,
BorderStyle =
{
Thickness = new Thickness(2, 2, 2, 2)
},
ThumbStyle =
{
BorderStyle =
{
Thickness = new Thickness(2, 2, 2, 2)
}
}
},
VScrollBarStyle =
{
ShowButtons = false,
Extent = 16,
BorderStyle =
{
Thickness = new Thickness(2, 2, 2, 2)
},
ThumbStyle =
{
BorderStyle =
{
Thickness = new Thickness(2, 2, 2, 2)
}
}
},
TextureFiltering = Miyagi.Common.TextureFiltering.Anisotropic,
Skin = MiyagiResources.Singleton.Skins["ConsolePanelSkin"]
};
consolePanel.SizeChanged += new EventHandler(consolePanel_SizeChanged);
consolePanel.LocationChanged += new EventHandler<ChangedValueEventArgs<Point>>(consolePanel_LocationChanged);
this.consoleTextbox = new TextBox("ConsoleTextbox")
{
Size = new Size(800, 30),
Location = new Point(0, 300),
Padding = new Thickness(5, 3, 5, 3),
AlwaysOnTop = false,
DefocusOnSubmit = false,
TextStyle =
{
Offset = new Point(0,3),
Alignment = Alignment.MiddleLeft,
ForegroundColour = Colours.White
},
TextBoxStyle =
{
CaretStyle =
{
Size = new Size(2, 16),
Colour = Colours.White
}
},
Skin = MiyagiResources.Singleton.Skins["ConsoleTextBoxSkin"],
ClearTextOnSubmit = true
};
consoleTextbox.Submit += new EventHandler<ValueEventArgs<string>>(consoleTextbox_Submit);
consoleGUI.Controls.Add(consolePanel);
consoleGUI.Controls.Add(consoleTextbox);
consoleGUI.ZOrder = 10;
mMiyagiRes.mMiyagiSystem.GUIManager.GUIs.Add(consoleGUI);
Visible = false;
}
public void AddLine(string text)
{
var label = new Label
{
Location = new Point(0, currentYpos),
Text = text,
AutoSize = true
};
label.SuccessfulHitTest += (s, e) => e.Cancel = true;
this.consolePanel.Controls.Add(label);
if (!consoleGUI.Visible) //Fuck miyagi. If the ui is hidden the size of it is 0,0. So here. Ugly hack.
{
consoleGUI.Visible = true;
this.currentYpos += label.Size.Height;
consoleGUI.Visible = false;
}
else this.currentYpos += label.Size.Height;
this.entries.Add(label);
this.log.Add(text);
if (entries.Count > maxLines) Trim();
this.consolePanel.ScrollToBottom();
}
public void AddLine(string text, Colour colour)
{
var label = new Label
{
Location = new Point(0, currentYpos),
Text = text,
AutoSize = true,
TextStyle =
{
ForegroundColour = colour
}
};
label.SuccessfulHitTest += (s, e) => e.Cancel = true;
this.consolePanel.Controls.Add(label);
if (!consoleGUI.Visible) //Fuck miyagi. If the ui is hidden the size of it is 0,0. So here. Ugly hack.
{
consoleGUI.Visible = true;
this.currentYpos += label.Size.Height;
consoleGUI.Visible = false;
}
else this.currentYpos += label.Size.Height;
this.entries.Add(label);
this.log.Add(text);
if (entries.Count > maxLines) Trim();
this.consolePanel.ScrollToBottom();
}
void consoleTextbox_Submit(object sender, ValueEventArgs<string> e)
{
processInput(e.Data);
}
void Trim()
{
if (entries.Count < 2) return; //This should never happen. Just make sure maxlines is > 2.
entries[1].Location = new Point(0, 0);
Label toDelete = entries[0];
entries.RemoveAt(0); //Remove the oldest element.
currentYpos -= toDelete.Size.Height;
toDelete.Dispose();
for (int i = 0; i < entries.Count; i++) //Update the positions of the other elements.
{
entries[i].Location = (i != 0) ? new Point(0, entries[i - 1].Location.Y + entries[i - 1].Size.Height) : new Point(0, 0);
}
}
private void processInput(string text)
{
string[] seperator = new string[] {" "};
string[] splitInput = text.Split(seperator, StringSplitOptions.RemoveEmptyEntries);
if (splitInput.Length < 1) return;
switch (splitInput[0].ToLowerInvariant())
{
case("exit"):
mEngine.Window.Destroy();
break;
case("setstate"): //This doesnt quite work yet. Im terrible at this.
if (splitInput.Length < 2)
{
AddLine("State name required", Colours.Red);
return;
}
Type stateType = Type.GetType("SS3D.States." + splitInput[1], false, true);
if(stateType == null)
{
AddLine("Unknown State : " + splitInput[1], Colours.Red);
return;
}
AddLine("Switching to state : " + stateType.Name, Colours.Green);
mEngine.mStateMgr.RequestStateChange(stateType);
break;
case ("getstate"):
AddLine("Current State : " + mEngine.mStateMgr.mCurrentState.GetType().Name, Colours.Green);
break;
case("cls"):
clear();
break;
default:
AddLine("Invalid command : " + splitInput[0], Colours.Red);
break;
}
}
void clear()
{
foreach (Label lbl in entries)
{
lbl.Dispose();
}
currentYpos = 0;
consolePanel.ScrollToTop();
}
void consolePanel_LocationChanged(object sender, ChangedValueEventArgs<Point> e)
{
if (consoleTextbox != null)
{
Point newLoc = new Point(consolePanel.Location.X, consolePanel.Location.Y + consolePanel.Size.Height);
consoleTextbox.Location = newLoc;
}
}
void consolePanel_SizeChanged(object sender, EventArgs e)
{
if (consoleTextbox != null)
{
Size newSize = new Size(consolePanel.Size.Width, 30);
Point newLoc = new Point(consolePanel.Location.X, consolePanel.Location.Y + consolePanel.Size.Height);
consoleTextbox.Size = newSize;
consoleTextbox.Location = newLoc;
}
}
}
}

226
SS3D_Client/Program.cs Normal file
View File

@@ -0,0 +1,226 @@
using System;
using MOIS;
using Mogre;
using Miyagi;
using Miyagi.Common;
using Miyagi.Common.Data;
using Miyagi.Common.Resources;
using Miyagi.Plugin.Input.Mois;
using SS3D.Modules;
using SS3D.States;
using SS3D.Modules.Network;
using SS3D.Modules.UI;
using Lidgren;
using Lidgren.Network;
namespace SS3D
{
public class Program
{
private static OgreManager mEngine;
private static StateManager mStateMgr;
private static MOIS.InputManager mInputMgr;
private uint consoleSeparationCounter = 0;
private MiyagiSystem mMiyagiSystem;
private NetworkManager mNetworkMgr;
private MOIS.Keyboard mKeyboard;
private MOIS.Mouse mMouse;
//Create network manager.
/************************************************************************/
/* program starts here */
/************************************************************************/
[STAThread]
static void Main()
{
// create Ogre manager
mEngine = new OgreManager();
// create state manager
mStateMgr = mEngine.mStateMgr = new StateManager( mEngine );
// create main program
Program prg = new Program();
//Create Miyagi
prg.mMiyagiSystem = new MiyagiSystem("Mogre");
mEngine.mMiyagiSystem = prg.mMiyagiSystem;
//Create & Init Miyagi Resource Manager - Must happen BEFORE resources are loaded.
MiyagiResources.Singleton.Initialize(prg.mMiyagiSystem);
//Load Config.
ConfigManager.Singleton.Initialize("./config.xml");
//Create Network Manager
prg.mNetworkMgr = new NetworkManager(mEngine, mStateMgr);
mEngine.mNetworkMgr = prg.mNetworkMgr;
//try to initialize Ogre
if (mEngine.Startup(ConfigManager.Singleton.Configuration))
{
//Load Resources. If this doesnt work because some user made a mistake
//while editing the config - the program will crash.
ConfigManager.Singleton.LoadResources();
//Create Console
GameConsole.Singleton.Initialize(mEngine);
//Create input handlers
int windowHandle;
mEngine.Window.GetCustomAttribute("WINDOW", out windowHandle);
mInputMgr = MOIS.InputManager.CreateInputSystem((uint)windowHandle);
prg.mKeyboard = (MOIS.Keyboard)mInputMgr.CreateInputObject(MOIS.Type.OISKeyboard, true);
prg.mMouse = (MOIS.Mouse)mInputMgr.CreateInputObject(MOIS.Type.OISMouse, true);
prg.mMiyagiSystem.PluginManager.LoadPlugin("Miyagi.Plugin.Input.Mois.dll", prg.mKeyboard, prg.mMouse);
LogManager.Singleton.LogMessage("Managers loaded successfully.");
//Set Mouse Area
MOIS.MouseState_NativePtr state = prg.mMouse.MouseState;
state.width = (int)mEngine.Window.Width;
state.height = (int)mEngine.Window.Height;
//Setup input events.
mEngine.Root.FrameStarted += new FrameListener.FrameStartedHandler(prg.FrameStarted);
prg.mKeyboard.KeyPressed += new KeyListener.KeyPressedHandler(prg.ProcessKeyPress);
prg.mKeyboard.KeyReleased += new KeyListener.KeyReleasedHandler(prg.ProcessKeyRelease);
prg.mMouse.MousePressed += new MouseListener.MousePressedHandler(prg.ProcessMousePress);
prg.mMouse.MouseReleased += new MouseListener.MouseReleasedHandler(prg.ProcessMouseRelease);
prg.mMouse.MouseMoved += new MouseListener.MouseMovedHandler(prg.ProcessMouseMove);
mEngine.Window.SetDeactivateOnFocusChange(false);
//Try to initialize the state manager.
if (mStateMgr.Startup(typeof(MainMenu)))
{
//Run main loop until the window is closed
while (!mEngine.Window.IsClosed)
{
// update networking
mEngine.mNetworkMgr.UpdateNetwork();
// update the objects in the scene
prg.UpdateScene();
// update Ogre and render the current frame
mEngine.Update();
}
}
}
// shutdown networking.
mEngine.mNetworkMgr.ShutDown();
// shut down state manager
mStateMgr.Shutdown();
// shutdown interface
prg.mMiyagiSystem.Dispose();
prg.mMiyagiSystem = null;
// shut down Ogre
mEngine.Shutdown();
}
#region Update, Create and Remove scene , Constructor
public Program()
{
//Constructor
}
public void UpdateScene()
{
//update the state manager - this will update the active state.
mStateMgr.Update(0);
}
bool FrameStarted(FrameEvent evt)
{
try
{
mKeyboard.Capture();
mMouse.Capture();
}
catch(Exception e)
{
e.ToString(); //Just to make VS shut up.
//LogManager.Singleton.LogMessage("MOIS: Input Capture failed -> " + e.Message);
}
if (mMiyagiSystem != null)
mEngine.mMiyagiSystem.Update();
ProcessUnbufferedInput(evt);
return true;
}
#endregion
#region Input
bool ProcessUnbufferedInput(FrameEvent evt)
{
mStateMgr.UpdateInput(evt, mKeyboard, mMouse);
return true;
}
bool ProcessKeyPress(KeyEvent evt)
{
if (evt.key == MOIS.KeyCode.KC_ESCAPE)
mEngine.Window.Destroy();
if (evt.key == MOIS.KeyCode.KC_F12)
GameConsole.Singleton.Visible = !GameConsole.Singleton.Visible;
if (evt.key == MOIS.KeyCode.KC_F11)
GameConsole.Singleton.AddLine("Abs X: " + (mMiyagiSystem.GUIManager.Cursor.Location.X / (float)mEngine.Window.Width).ToString() + " / Abs Y: " + (mMiyagiSystem.GUIManager.Cursor.Location.Y / (float)mEngine.Window.Height).ToString(), Colours.MediumBlue);
if (evt.key == MOIS.KeyCode.KC_F10)
{
GameConsole.Singleton.AddLine("***" + consoleSeparationCounter.ToString() + "***", Colours.MediumSlateBlue);
consoleSeparationCounter++;
}
mStateMgr.KeyDown(evt);
return true;
}
bool ProcessKeyRelease(KeyEvent evt)
{
mStateMgr.KeyUp(evt);
return true;
}
bool ProcessMousePress(MouseEvent evt, MouseButtonID button)
{
mStateMgr.MouseDown(evt,button);
return true;
}
bool ProcessMouseRelease(MouseEvent evt, MouseButtonID button)
{
mStateMgr.MouseUp(evt, button);
return true;
}
bool ProcessMouseMove(MouseEvent evt)
{
mStateMgr.MouseMove(evt);
return true;
}
#endregion
}
}

View File

@@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle( "SpaceStation3D" )]
[assembly: AssemblyDescription( "___" )]
[assembly: AssemblyConfiguration( "" )]
[assembly: AssemblyCompany( "___" )]
[assembly: AssemblyProduct("SpaceStation3D")]
[assembly: AssemblyCopyright( "___" )]
[assembly: AssemblyTrademark( "" )]
[assembly: AssemblyCulture( "" )]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible( false )]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid( "1f0dcf52-ea1e-47d7-8c7f-b832d73c38a6" )]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion( "1.0.0.0" )]
[assembly: AssemblyFileVersion( "1.0.0.0" )]

View File

@@ -0,0 +1,188 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>9.0.30729</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{835500C3-34D6-4329-954A-5D028D634E90}</ProjectGuid>
<OutputType>WinExe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>SS3D</RootNamespace>
<AssemblyName>SpaceStation3D</AssemblyName>
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<IsWebBootstrapper>false</IsWebBootstrapper>
<PublishUrl>publish\</PublishUrl>
<Install>true</Install>
<InstallFrom>Disk</InstallFrom>
<UpdateEnabled>false</UpdateEnabled>
<UpdateMode>Foreground</UpdateMode>
<UpdateInterval>7</UpdateInterval>
<UpdateIntervalUnits>Days</UpdateIntervalUnits>
<UpdatePeriodically>false</UpdatePeriodically>
<UpdateRequired>false</UpdateRequired>
<MapFileExtensions>true</MapFileExtensions>
<ApplicationRevision>0</ApplicationRevision>
<ApplicationVersion>1.0.0.%2a</ApplicationVersion>
<UseApplicationTrust>false</UseApplicationTrust>
<BootstrapperEnabled>true</BootstrapperEnabled>
<FileUpgradeFlags>
</FileUpgradeFlags>
<OldToolsVersion>3.5</OldToolsVersion>
<UpgradeBackupLocation />
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<PlatformTarget>x86</PlatformTarget>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<UseVSHostingProcess>false</UseVSHostingProcess>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<PlatformTarget>x86</PlatformTarget>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<UseVSHostingProcess>false</UseVSHostingProcess>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x86\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DebugType>full</DebugType>
<PlatformTarget>x86</PlatformTarget>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
<OutputPath>bin\x86\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x86</PlatformTarget>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>
<ItemGroup>
<Reference Include="Lidgren.Network, Version=2010.7.15.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\References\Release\Lidgren.Network.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Miyagi">
<HintPath>..\References\Debug\Miyagi.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Miyagi.Backend.Mogre">
<HintPath>..\References\Debug\Backends\Miyagi.Backend.Mogre.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Miyagi.Plugin.Input.Mois">
<HintPath>..\References\Debug\Plugins\Miyagi.Plugin.Input.Mois.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Mogre, Version=1.7.1.0, Culture=neutral, processorArchitecture=x86">
<HintPath>..\References\Debug\Mogre.dll</HintPath>
<SpecificVersion>False</SpecificVersion>
<Private>False</Private>
</Reference>
<Reference Include="MOIS, Version=1.1.0.0, Culture=neutral, processorArchitecture=x86">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\References\Debug\MOIS.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
<Reference Include="System.Data" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" />
<Reference Include="System.Xml.Linq">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="HelperClasses\Rect2D.cs" />
<Compile Include="Modules\ConfigManager.cs" />
<Compile Include="Modules\Items\ItemManager.cs" />
<Compile Include="Modules\Map\Map.cs" />
<Compile Include="Modules\Map\MapSaver.cs" />
<Compile Include="Modules\MiyagiRessources.cs" />
<Compile Include="Modules\Mobs\MobManager.cs" />
<Compile Include="Modules\Network\NetworkManager.cs" />
<Compile Include="Modules\OgreEventArgs.cs" />
<Compile Include="Modules\OgreManager.cs" />
<Compile Include="Modules\State.cs" />
<Compile Include="Modules\StateManager.cs" />
<Compile Include="Modules\UI\Console.cs" />
<Compile Include="Modules\UI\Chatbox.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="States\ConnectMenu.cs" />
<Compile Include="States\GameScreen.cs" />
<Compile Include="States\LoadingScreen.cs" />
<Compile Include="States\EditScreen.cs" />
<Compile Include="States\LobbyScreen.cs" />
<Compile Include="States\Options.cs" />
<Compile Include="States\MainMenu.cs" />
</ItemGroup>
<ItemGroup>
<BootstrapperPackage Include="Microsoft.Net.Client.3.5">
<Visible>False</Visible>
<ProductName>.NET Framework Client Profile</ProductName>
<Install>false</Install>
</BootstrapperPackage>
<BootstrapperPackage Include="Microsoft.Net.Framework.2.0">
<Visible>False</Visible>
<ProductName>.NET Framework 2.0 %28x86%29</ProductName>
<Install>true</Install>
</BootstrapperPackage>
<BootstrapperPackage Include="Microsoft.Net.Framework.3.0">
<Visible>False</Visible>
<ProductName>.NET Framework 3.0 %28x86%29</ProductName>
<Install>false</Install>
</BootstrapperPackage>
<BootstrapperPackage Include="Microsoft.Net.Framework.3.5">
<Visible>False</Visible>
<ProductName>.NET Framework 3.5</ProductName>
<Install>false</Install>
</BootstrapperPackage>
<BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
<Visible>False</Visible>
<ProductName>.NET Framework 3.5 SP1</ProductName>
<Install>false</Install>
</BootstrapperPackage>
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SS3D_shared\SS3D_shared.csproj">
<Project>{4229D7E3-C3AE-4C0A-B2B1-BB20911150DC}</Project>
<Name>SS3D_shared</Name>
<Private>True</Private>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<PublishUrlHistory>publish\</PublishUrlHistory>
<InstallUrlHistory>
</InstallUrlHistory>
<SupportUrlHistory>
</SupportUrlHistory>
<UpdateUrlHistory>
</UpdateUrlHistory>
<BootstrapperUrlHistory>
</BootstrapperUrlHistory>
<ErrorReportUrlHistory>
</ErrorReportUrlHistory>
<FallbackCulture>en-US</FallbackCulture>
<VerifyUploadedFiles>false</VerifyUploadedFiles>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,215 @@
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Collections.Generic;
using Mogre;
using SS3D.Modules;
using Miyagi;
using Miyagi.UI;
using Miyagi.UI.Controls;
using Miyagi.Common;
using Miyagi.Common.Data;
using Miyagi.Common.Resources;
using Miyagi.Common.Events;
using Miyagi.TwoD;
namespace SS3D.States
{
public class ConnectMenu : State
{
private OgreManager mEngine;
private StateManager mStateMgr;
private GUI guiConnectMenu;
private string ipTextboxIP = "127.0.0.1";
private bool connecting = false;
private DateTime connectTime;
private float connectTimeOut = 5000f;
public ConnectMenu()
{
mEngine = null;
}
#region Startup, Shutdown, Update
public override bool Startup(StateManager _mgr)
{
mEngine = _mgr.Engine;
mStateMgr = _mgr;
mEngine.mNetworkMgr.Disconnect();
mEngine.mNetworkMgr.Connected += new Modules.Network.NetworkStateHandler(mNetworkMgr_Connected);
// Lets make sure the background is visible
if (mEngine.mMiyagiSystem.GUIManager.GetGUI("guiBackground") != null)
{
mEngine.mMiyagiSystem.GUIManager.GetGUI("guiBackground").Visible = true;
}
// If we've been here before, lets just use that menu and not recreated it
if (mEngine.mMiyagiSystem.GUIManager.GetGUI("guiConnectMenu") == null)
{
guiConnectMenu = new GUI("guiConnectMenu");
guiConnectMenu.ZOrder = 10;
mEngine.mMiyagiSystem.GUIManager.GUIs.Add(guiConnectMenu);
CreateMenu();
guiConnectMenu.Resize(mEngine.ScalarX, mEngine.ScalarY);
}
else
{
guiConnectMenu = mEngine.mMiyagiSystem.GUIManager.GetGUI("guiConnectMenu");
guiConnectMenu.Fade(0, 1, 100);
}
return true;
}
private void CreateMenu()
{
Button joinButton = new Button("connectJoinButton")
{
Location = new Point(650, 280),
Size = new Size(160, 40),
Skin = MiyagiResources.Singleton.Skins["ButtonSkinGreen"],
TextStyle =
{
Alignment = Alignment.MiddleCenter,
ForegroundColour = Colours.DarkBlue
}
};
joinButton.MouseDown += JoinButtonMouseDown;
joinButton.Text = "Connect";
guiConnectMenu.Controls.Add(joinButton);
Button returnButton = new Button("connectReturnButton")
{
Location = new Point(650, 330),
Size = new Size(160, 40),
Skin = MiyagiResources.Singleton.Skins["ButtonSkinGreen"],
TextStyle =
{
Alignment = Alignment.MiddleCenter,
ForegroundColour = Colours.DarkBlue
}
};
returnButton.MouseDown += ReturnButtonMouseDown;
returnButton.Text = "Return";
guiConnectMenu.Controls.Add(returnButton);
TextBox ipTextbox = new TextBox("connectIpTextbox")
{
Size = new Size(160, 32),
Location = new Point(820, 282),
Padding = new Thickness(2, 2, 2, 2),
Text = ipTextboxIP,
TextStyle =
{
Alignment = Alignment.MiddleLeft,
},
TextBoxStyle =
{
CaretStyle =
{
Size = new Size(2, 16),
Colour = Colours.Black
}
},
Skin = MiyagiResources.Singleton.Skins["TextBoxSkin"]
};
ipTextbox.TextChanged += ipTextBoxChanged;
guiConnectMenu.Controls.Add(ipTextbox);
}
private void ipTextBoxChanged(object sender, Miyagi.Common.Events.TextEventArgs e)
{
ipTextboxIP = ((TextBox)sender).Text;
}
void mNetworkMgr_Connected(Modules.Network.NetworkManager netMgr)
{
connecting = false;
//guiConnectMenu.GetControl("connectJoinButton").Enabled = true;
//((Button)guiConnectMenu.GetControl("connectJoinButton")).Text = "Join";
mStateMgr.RequestStateChange(typeof(GameScreen));
}
private void JoinButtonMouseDown(object sender, MouseButtonEventArgs e)
{
connectTime = DateTime.Now;
connecting = true;
mEngine.mNetworkMgr.ConnectTo(ipTextboxIP);
guiConnectMenu.GetControl("connectJoinButton").Enabled = false;
((Button)guiConnectMenu.GetControl("connectJoinButton")).Text = "Trying";
}
private void ReturnButtonMouseDown(object sender, MouseButtonEventArgs e)
{
if (connecting)
{
connecting = false;
guiConnectMenu.GetControl("connectJoinButton").Enabled = true;
((Button)guiConnectMenu.GetControl("connectJoinButton")).Text = "Retry";
mEngine.mNetworkMgr.Disconnect();
}
mStateMgr.RequestStateChange(typeof(MainMenu));
}
public override void Shutdown()
{
mEngine.mNetworkMgr.Connected -= new Modules.Network.NetworkStateHandler(mNetworkMgr_Connected);
guiConnectMenu.Fade(1, 0, 100);
}
public override void Update(long _frameTime)
{
if (connecting)
{
TimeSpan dif = DateTime.Now - connectTime;
if (dif.TotalMilliseconds > connectTimeOut)
{
connecting = false;
guiConnectMenu.GetControl("connectJoinButton").Enabled = true;
((Button)guiConnectMenu.GetControl("connectJoinButton")).Text = "Retry";
mEngine.mNetworkMgr.Disconnect();
}
}
}
#endregion
#region Input
public override void UpdateInput(Mogre.FrameEvent evt, MOIS.Keyboard keyState, MOIS.Mouse mouseState)
{
}
public override void KeyDown(MOIS.KeyEvent keyState)
{
}
public override void KeyUp(MOIS.KeyEvent keyState)
{
}
public override void MouseUp(MOIS.MouseEvent mouseState, MOIS.MouseButtonID button)
{
}
public override void MouseDown(MOIS.MouseEvent mouseState, MOIS.MouseButtonID button)
{
}
public override void MouseMove(MOIS.MouseEvent mouseState)
{
}
#endregion
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,211 @@
using System;
using System.IO;
using Mogre;
using Lidgren.Network;
using SS3D.Modules;
using SS3D.Modules.Map;
using SS3D.Modules.Items;
using SS3D.Modules.Mobs;
using SS3D.Modules.Network;
using SS3D_shared;
using System.Collections.Generic;
using System.Reflection;
using Miyagi;
using Miyagi.UI;
using Miyagi.UI.Controls;
using Miyagi.Common;
using Miyagi.Common.Data;
using Miyagi.Common.Resources;
using Miyagi.Common.Events;
using Miyagi.TwoD;
namespace SS3D.States
{
public class GameScreen : State
{
#region Variables
private OgreManager mEngine;
private StateManager mStateMgr;
private Map map;
private ItemManager itemManager;
private MobManager mobManager;
private GUI guiGameScreen;
#endregion
public GameScreen()
{
mEngine = null;
}
#region Startup, Shutdown, Update
public override bool Startup(StateManager _mgr)
{
mEngine = _mgr.Engine;
mStateMgr = _mgr;
map = new Map(mEngine);
itemManager = new ItemManager(mEngine, map, mEngine.mNetworkMgr);
mobManager = new MobManager(mEngine, map, mEngine.mNetworkMgr);
mEngine.Camera.Position = new Mogre.Vector3(0, 300, 0);
mEngine.Camera.LookAt(new Mogre.Vector3(160,64,160));
SetUp();
mEngine.mNetworkMgr.MessageArrived += new NetworkMsgHandler(mNetworkMgr_MessageArrived);
mEngine.mNetworkMgr.SetMap(map);
mEngine.mNetworkMgr.RequestMap();
mEngine.mMiyagiSystem.GUIManager.DisposeAllGUIs();
return true;
}
private void SetUp()
{
mEngine.SceneMgr.ShadowTextureSelfShadow = true;
mEngine.SceneMgr.SetShadowTextureCasterMaterial("shadow_caster");
mEngine.SceneMgr.SetShadowTexturePixelFormat(PixelFormat.PF_FLOAT16_RGB);
mEngine.SceneMgr.ShadowCasterRenderBackFaces = false;
mEngine.SceneMgr.SetShadowTextureSize(512);
mEngine.SceneMgr.ShadowTechnique = ShadowTechnique.SHADOWTYPE_TEXTURE_ADDITIVE_INTEGRATED;
mEngine.SceneMgr.AmbientLight = ColourValue.White;
mEngine.SceneMgr.SetSkyBox(true, "SkyBox", 900f, true);
}
public override void Shutdown()
{
mEngine.mMiyagiSystem.GUIManager.GUIs.Remove(guiGameScreen);
map.Shutdown();
map = null;
itemManager.Shutdown();
itemManager = null;
mobManager.Shutdown();
mobManager = null;
mEngine.mNetworkMgr.Disconnect();
}
public override void Update(long _frameTime)
{
itemManager.Update();
mobManager.Update();
}
private void mNetworkMgr_MessageArrived(NetworkManager netMgr, NetIncomingMessage msg)
{
if (msg == null)
{
return;
}
switch (msg.MessageType)
{
case NetIncomingMessageType.Data:
NetMessage messageType = (NetMessage)msg.ReadByte();
switch (messageType)
{
case NetMessage.ChangeTile:
ChangeTile(msg);
break;
case NetMessage.ItemMessage:
itemManager.HandleNetworkMessage(msg);
break;
case NetMessage.MobMessage:
mobManager.HandleNetworkMessage(msg);
break;
case NetMessage.SendMap:
RecieveMap(msg);
break;
default:
break;
}
break;
default:
break;
}
}
public void RecieveMap(NetIncomingMessage msg)
{
int mapWidth = msg.ReadInt32();
int mapHeight = msg.ReadInt32();
TileType[,] tileArray = new TileType[mapWidth, mapHeight];
for (int x = 0; x < mapWidth; x++)
{
for (int z = 0; z < mapHeight; z++)
{
tileArray[x, z] = (TileType)msg.ReadByte();
}
}
map.LoadNetworkedMap(tileArray, mapWidth, mapHeight);
}
private void ChangeTile(NetIncomingMessage msg)
{
if (map == null)
{
return;
}
int x = msg.ReadInt32();
int z = msg.ReadInt32();
TileType newTile = (TileType)msg.ReadByte();
map.ChangeTile(x, z, newTile);
}
#endregion
#region Input
public override void UpdateInput(Mogre.FrameEvent evt, MOIS.Keyboard keyState, MOIS.Mouse mouseState)
{
if(keyState.IsKeyDown(MOIS.KeyCode.KC_W))
{
mobManager.MoveMe(1);
}
if(keyState.IsKeyDown(MOIS.KeyCode.KC_D))
{
mobManager.MoveMe(2);
}
if (keyState.IsKeyDown(MOIS.KeyCode.KC_A))
{
mobManager.MoveMe(3);
}
if (keyState.IsKeyDown(MOIS.KeyCode.KC_S))
{
mobManager.MoveMe(4);
}
}
public override void KeyDown(MOIS.KeyEvent keyState)
{
}
public override void KeyUp(MOIS.KeyEvent keyState)
{
}
public override void MouseUp(MOIS.MouseEvent mouseState, MOIS.MouseButtonID button)
{
}
public override void MouseDown(MOIS.MouseEvent mouseState, MOIS.MouseButtonID button)
{
}
public override void MouseMove(MOIS.MouseEvent mouseState)
{
}
#endregion
}
}

View File

@@ -0,0 +1,73 @@
using System;
using Mogre;
using SS3D.Modules;
using SS3D.Modules.Map;
using SS3D.Modules.Network;
using System.Collections.Generic;
using System.Reflection;
namespace SS3D.States
{
public class LoadingScreen : State
{
private OgreManager mEngine;
private StateManager mStateMgr;
private NetworkManager mNetworkMgr;
public LoadingScreen()
{
mEngine = null;
}
#region Startup, Shutdown, Update
public override bool Startup(StateManager _mgr)
{
mEngine = _mgr.Engine;
mStateMgr = _mgr;
mNetworkMgr = mEngine.mNetworkMgr;
return true;
}
public override void Shutdown()
{
}
public override void Update(long _frameTime)
{
}
#endregion
#region Input
public override void UpdateInput(Mogre.FrameEvent evt, MOIS.Keyboard keyState, MOIS.Mouse mouseState)
{
}
public override void KeyDown(MOIS.KeyEvent keyState)
{
}
public override void KeyUp(MOIS.KeyEvent keyState)
{
}
public override void MouseUp(MOIS.MouseEvent mouseState, MOIS.MouseButtonID button)
{
}
public override void MouseDown(MOIS.MouseEvent mouseState, MOIS.MouseButtonID button)
{
}
public override void MouseMove(MOIS.MouseEvent mouseState)
{
}
#endregion
}
}

View File

@@ -0,0 +1,163 @@
using System;
using Mogre;
using Lidgren.Network;
using SS3D.Modules;
using SS3D.Modules.Map;
using SS3D.Modules.Network;
using SS3D.Modules.UI;
using System.Collections.Generic;
using System.Reflection;
namespace SS3D.States
{
public class LobbyScreen : State
{
private OgreManager mEngine;
private StateManager mStateMgr;
private int serverMaxPlayers;
private Chatbox lobbyChat;
public LobbyScreen()
{
mEngine = null;
}
public override bool Startup(StateManager _mgr)
{
mEngine = _mgr.Engine;
mStateMgr = _mgr;
mEngine.mNetworkMgr.MessageArrived += new NetworkMsgHandler(mNetworkMgr_MessageArrived);
lobbyChat = new Chatbox("lobbyChat");
mEngine.mMiyagiSystem.GUIManager.GUIs.Add(lobbyChat.chatGUI);
lobbyChat.chatPanel.ResizeMode = Miyagi.UI.ResizeModes.None;
lobbyChat.chatPanel.Movable = false;
//guiConnectMenu.Resize(mEngine.ScalarX, mEngine.ScalarY);
NetworkManager netMgr = mEngine.mNetworkMgr;
NetOutgoingMessage message = netMgr.netClient.CreateMessage();
message.Write((byte)NetMessage.WelcomeMessage); //Request Welcome msg.
netMgr.netClient.SendMessage(message, NetDeliveryMethod.ReliableOrdered);
return true;
}
void mNetworkMgr_MessageArrived(NetworkManager netMgr, NetIncomingMessage msg)
{
switch (msg.MessageType)
{
case NetIncomingMessageType.Data:
NetMessage messageType = (NetMessage)msg.ReadByte();
switch (messageType)
{
case NetMessage.LobbyChat:
string text = msg.ReadString();
AddChat(text);
break;
case NetMessage.PlayerCount:
int newCount = msg.ReadByte();
break;
case NetMessage.WelcomeMessage:
HandleWelcomeMessage(msg);
break;
default:
break;
}
break;
default:
break;
}
}
private void AddChat(string text)
{
}
//public void clientName_OnSubmit(UiTextbox source, string the_text)
//{
// while (the_text.Length < 3)
// {
// the_text += "_";
// }
// the_text = the_text.Replace(' ', '_');
// the_text = the_text.Trim();
// source.SetText(the_text);
// mEngine.mNetworkMgr.SendClientName(the_text);
//}
//public void playButton_OnPress(UiButton source, MOIS.MouseButtonID button)
//{
// mStateMgr.RequestStateChange(typeof(EditScreen));
//}
//public void menuButton_OnPress(UiButton source, MOIS.MouseButtonID button)
//{
// mStateMgr.RequestStateChange(typeof(MainMenu));
// mEngine.mNetworkMgr.Disconnect();
//}
//public void chatBox_OnSubmit(UiTextbox source, string the_text)
//{
// mEngine.mNetworkMgr.SendLobbyChat(the_text);
// source.SetText("");
//}
public void SendLobbyChat(string text)
{
NetworkManager netMgr = mEngine.mNetworkMgr;
NetOutgoingMessage message = netMgr.netClient.CreateMessage();
message.Write((byte)NetMessage.LobbyChat);
message.Write(text);
netMgr.netClient.SendMessage(message, NetDeliveryMethod.ReliableOrdered);
}
public override void Shutdown()
{
mEngine.mNetworkMgr.MessageArrived -= new NetworkMsgHandler(mNetworkMgr_MessageArrived);
}
public override void Update(long _frameTime)
{
}
private void HandleWelcomeMessage(NetIncomingMessage msg)
{
string welcomeString = msg.ReadString();
serverMaxPlayers = msg.ReadInt32();
string serverMapNames = msg.ReadString();
GameType gameType = (GameType)msg.ReadByte();
}
#region Input
public override void UpdateInput(Mogre.FrameEvent evt, MOIS.Keyboard keyState, MOIS.Mouse mouseState)
{
}
public override void KeyDown(MOIS.KeyEvent keyState)
{
}
public override void KeyUp(MOIS.KeyEvent keyState)
{
}
public override void MouseUp(MOIS.MouseEvent mouseState, MOIS.MouseButtonID button)
{
}
public override void MouseDown(MOIS.MouseEvent mouseState, MOIS.MouseButtonID button)
{
}
public override void MouseMove(MOIS.MouseEvent mouseState)
{
}
#endregion
}
}

View File

@@ -0,0 +1,232 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Miyagi;
using Miyagi.UI;
using Miyagi.UI.Controls;
using Miyagi.Common;
using Miyagi.Common.Data;
using Miyagi.Common.Resources;
using Miyagi.Common.Events;
using Miyagi.TwoD;
using Mogre;
using SS3D.Modules;
namespace SS3D.States
{
public class MainMenu : State
{
private OgreManager mEngine;
private StateManager mStateMgr;
private GUI guiMainMenu;
private GUI guiBackground;
private Label infoLabel;
public MainMenu()
{
mEngine = null;
}
#region Startup, Shutdown, Update
public override bool Startup(StateManager _mgr)
{
mEngine = _mgr.Engine;
mStateMgr = _mgr;
// If the menus haven't been generated before, lets do that now, if they have, lets just use them.
if (mEngine.mMiyagiSystem.GUIManager.GetGUI("guiMainMenu") == null || mEngine.mMiyagiSystem.GUIManager.GetGUI("guiBackground") == null)
{
CreateMenu();
guiMainMenu.Fade(0, 1, 200);
guiBackground.Fade(0, 1, 200);
guiMainMenu.Visible = true;
guiBackground.Visible = true;
}
else
{
guiMainMenu = mEngine.mMiyagiSystem.GUIManager.GetGUI("guiMainMenu");
guiBackground = mEngine.mMiyagiSystem.GUIManager.GetGUI("guiBackground");
guiMainMenu.Fade(0, 1, 100);
guiMainMenu.Visible = true;
guiBackground.Visible = true;
}
guiMainMenu.EnsureZOrder();
guiBackground.EnsureZOrder();
return true;
}
public void CreateMenu()
{
mEngine.mMiyagiSystem.GUIManager.Cursor = new Cursor(MiyagiResources.Singleton.Skins["CursorSkin"], new Size(16, 16), Point.Empty, true);
guiMainMenu = new GUI("guiMainMenu");
guiBackground = new GUI("guiBackground");
Button editButton = new Button("mainEditButton")
{
Location = new Point(650, 280),
Size = new Size(160, 40),
Skin = MiyagiResources.Singleton.Skins["ButtonSkinGreen"],
TextStyle =
{
Alignment = Alignment.MiddleCenter,
ForegroundColour = Colours.DarkBlue,
Font = MiyagiResources.Singleton.Fonts["SpacedockStencil"]
}
};
editButton.MouseDown += EditButtonMouseDown;
editButton.Text = "Map Edit";
guiMainMenu.Controls.Add(editButton);
Button optionsButton = new Button("mainOptionsButton")
{
Location = new Point(650, 330),
Size = new Size(160, 40),
Skin = MiyagiResources.Singleton.Skins["ButtonSkinGreen"],
TextStyle =
{
Alignment = Alignment.MiddleCenter,
ForegroundColour = Colours.DarkBlue,
Font = MiyagiResources.Singleton.Fonts["SpacedockStencil"]
}
};
optionsButton.MouseDown += OptionsButtonMouseDown;
optionsButton.Text = "Options";
guiMainMenu.Controls.Add(optionsButton);
Button connectButton = new Button("mainConnectButton")
{
Location = new Point(820, 280),
Size = new Size(160, 40),
Skin = MiyagiResources.Singleton.Skins["ButtonSkinGreen"],
TextStyle =
{
Alignment = Alignment.MiddleCenter,
ForegroundColour = Colours.DarkBlue,
Font = MiyagiResources.Singleton.Fonts["SpacedockStencil"]
}
};
connectButton.MouseDown += ConnectButtonMouseDown;
connectButton.Text = "Connect";
guiMainMenu.Controls.Add(connectButton);
Button exitButton = new Button("mainExitButton")
{
Location = new Point(820, 330),
Size = new Size(160, 40),
Skin = MiyagiResources.Singleton.Skins["ButtonSkinGreen"],
TextStyle =
{
Alignment = Alignment.MiddleCenter,
ForegroundColour = Colours.DarkBlue,
Font = MiyagiResources.Singleton.Fonts["SpacedockStencil"]
}
};
exitButton.MouseDown += ExitButtonMouseDown;
exitButton.Text = "Exit";
guiMainMenu.Controls.Add(exitButton);
infoLabel = new Label("mainInfoLabel")
{
Size = new Size((int)mEngine.Window.Width, 24),
Location = new Point(24, (int)mEngine.Window.Height - 24),
Text = "Main menu loaded.",
TextStyle =
{
Font = MiyagiResources.Singleton.Fonts["SpacedockStencilSmall"]
}
};
guiMainMenu.Controls.Add(infoLabel);
Panel guiBackgroundPanel = new Panel("mainGuiBackgroundPanel")
{
Location = new Point(0, 0),
Size = new Size((int)mEngine.Window.Width, (int)mEngine.Window.Height),
ResizeMode = ResizeModes.None,
Skin = MiyagiResources.Singleton.Skins["MainBackground"],
AlwaysOnTop = false,
TextureFiltering = TextureFiltering.Anisotropic
};
guiBackground.Controls.Add(guiBackgroundPanel);
guiMainMenu.ZOrder = 10;
guiBackground.ZOrder = 5;
mEngine.mMiyagiSystem.GUIManager.GUIs.Add(guiBackground);
mEngine.mMiyagiSystem.GUIManager.GUIs.Add(guiMainMenu);
guiMainMenu.Resize(mEngine.ScalarX, mEngine.ScalarY);
guiMainMenu.Visible = true;
guiBackground.Visible = true;
}
private void EditButtonMouseDown(object sender, MouseButtonEventArgs e)
{
mStateMgr.RequestStateChange(typeof(EditScreen));
}
private void ConnectButtonMouseDown(object sender, MouseButtonEventArgs e)
{
mStateMgr.RequestStateChange(typeof(ConnectMenu));
}
private void OptionsButtonMouseDown(object sender, MouseButtonEventArgs e)
{
((Label)guiMainMenu.GetControl("mainInfoLabel")).Text = "Options pressed";
mStateMgr.RequestStateChange(typeof(OptionsMenu));
}
private void ExitButtonMouseDown(object sender, MouseButtonEventArgs e)
{
((Label)guiMainMenu.GetControl("mainInfoLabel")).Text = "Exit pressed";
mEngine.Window.Destroy();
}
public override void Shutdown()
{
guiBackground.Visible = false;
guiMainMenu.Fade(1, 0, 100);
}
public override void Update(long _frameTime)
{
}
#endregion
#region Input
public override void UpdateInput(Mogre.FrameEvent evt, MOIS.Keyboard keyState, MOIS.Mouse mouseState)
{
}
public override void KeyDown(MOIS.KeyEvent keyState)
{
}
public override void KeyUp(MOIS.KeyEvent keyState)
{
}
public override void MouseUp(MOIS.MouseEvent mouseState, MOIS.MouseButtonID button)
{
}
public override void MouseDown(MOIS.MouseEvent mouseState, MOIS.MouseButtonID button)
{
}
public override void MouseMove(MOIS.MouseEvent mouseState)
{
}
#endregion
}
}

View File

@@ -0,0 +1,396 @@
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Collections.Generic;
using Mogre;
using SS3D.Modules;
using Miyagi;
using Miyagi.UI;
using Miyagi.UI.Controls;
using Miyagi.Common;
using Miyagi.Common.Data;
using Miyagi.Common.Resources;
using Miyagi.Common.Events;
using Miyagi.TwoD;
namespace SS3D.States
{
public class OptionsMenu : State
{
private OgreManager mEngine;
private StateManager mStateMgr;
private Dictionary<string, KeyValuePair<uint, uint>> possible_resolutions;
private GUI guiOptionsMenu;
private uint res_width = 0;
private uint res_heigth = 0;
private bool changed = false;
public OptionsMenu()
{
mEngine = null;
}
#region Startup, Shutdown, Update
public override bool Startup(StateManager _mgr)
{
mEngine = _mgr.Engine;
mStateMgr = _mgr;
// Lets make sure the background is visible
if (mEngine.mMiyagiSystem.GUIManager.GetGUI("guiBackground") != null)
{
mEngine.mMiyagiSystem.GUIManager.GetGUI("guiBackground").Visible = true;
}
// If we've been here before, lets just use that menu and not recreated it
if (mEngine.mMiyagiSystem.GUIManager.GetGUI("guiOptionsMenu") == null)
{
possible_resolutions = GetResolutions();
guiOptionsMenu = new GUI("guiOptionsMenu");
guiOptionsMenu.ZOrder = 10;
mEngine.mMiyagiSystem.GUIManager.GUIs.Add(guiOptionsMenu);
CreateMenu();
guiOptionsMenu.Resize(mEngine.ScalarX, mEngine.ScalarY);
}
else
{
guiOptionsMenu = mEngine.mMiyagiSystem.GUIManager.GetGUI("guiOptionsMenu");
guiOptionsMenu.Fade(0, 1, 100);
CheckBox cbFullscreen = (CheckBox)mEngine.mMiyagiSystem.GUIManager.GetControl("optionsFullscreenCheckbox");
cbFullscreen.Checked = ConfigManager.Singleton.Configuration.Fullscreen;
DropDownList resolutionsDropdown = (DropDownList)mEngine.mMiyagiSystem.GUIManager.GetControl("optionsResolutionsDropdown");
resolutionsDropdown.Text = ConfigManager.Singleton.Configuration.DisplayWidth.ToString() + " x " + ConfigManager.Singleton.Configuration.DisplayHeight.ToString();
}
return true;
}
private void CreateMenu()
{
Panel passportPanel = new Panel("optionsPassportPanel")
{
Location = new Point(0, 0),
Size = new Size(866, 768),
ResizeMode = ResizeModes.None,
Skin = MiyagiResources.Singleton.Skins["PassportOverlay"],
AlwaysOnTop = false,
TextureFiltering = TextureFiltering.Anisotropic,
Enabled = false
};
Panel ticketPanel = new Panel("optionsTicketPanel")
{
Location = new Point(0, 256),
Size = new Size(740, 356),
ResizeMode = ResizeModes.None,
Skin = MiyagiResources.Singleton.Skins["TicketOverlay"],
AlwaysOnTop = false,
TextureFiltering = TextureFiltering.Anisotropic,
Enabled = false
};
Button returnButton = new Button("optionsReturnButton")
{
Location = new Point(580, 350),
Size = new Size(120, 30),
Skin = MiyagiResources.Singleton.Skins["ButtonSkinLogo"],
Text = "Return",
TextStyle =
{
Alignment = Alignment.MiddleCenter,
ForegroundColour = Colours.DarkBlue,
Font = MiyagiResources.Singleton.Fonts["SpacedockStencil"]
}
};
Button applyButton = new Button("optionsApplyButton")
{
Location = new Point(580, 300),
Size = new Size(120, 30),
Skin = MiyagiResources.Singleton.Skins["ButtonSkinLogo"],
Text = "Save",
TextStyle =
{
Alignment = Alignment.MiddleCenter,
ForegroundColour = Colours.DarkBlue,
Font = MiyagiResources.Singleton.Fonts["SpacedockStencil"]
}
};
CheckBox fullscreenCheckbox = new CheckBox("optionsFullscreenCheckbox")
{
Location = new Point(200, 410),
Size = new Size(30, 30),
Skin = MiyagiResources.Singleton.Skins["CheckboxSkin"],
TextureFiltering = TextureFiltering.Anisotropic,
TextStyle =
{
Alignment = Alignment.MiddleCenter,
ForegroundColour = Colours.DarkBlue,
}
};
Label fullscreenCheckboxLabel = new Label("optionsFullscreenCheckboxLabel")
{
Location = new Point(fullscreenCheckbox.Location.X + fullscreenCheckbox.Size.Width + 10, fullscreenCheckbox.Location.Y + (fullscreenCheckbox.Size.Height / 4)),
Text = "Fullscreen",
AutoSize = true,
TextStyle =
{
Font = MiyagiResources.Singleton.Fonts["SpacedockStencilSmall"]
}
};
DropDownList resolutionsDropdown = new DropDownList("optionsResolutionsDropdown")
{
Location = new Point(20, 410),
Size = new Size(175, 30),
Skin = MiyagiResources.Singleton.Skins["DropDownListRedSkin"],
DropDownSize = new Size(175, 140),
TextureFiltering = TextureFiltering.Anisotropic,
ListStyle =
{
ItemOffset = new Point(10,0),
Alignment= Alignment.MiddleLeft,
MaxVisibleItems = 4,
Font = MiyagiResources.Singleton.Fonts["SpacedockStencilSmall"],
MultiSelect = false,
ScrollBarStyle =
{
Extent = 15, //Width of the scrollbar, if youre wondering.
ThumbStyle =
{
BorderStyle =
{
Thickness = new Thickness(2, 2, 2, 2)
}
}
},
},
TextStyle =
{
Offset = new Point(10, 0),
Alignment = Alignment.MiddleLeft,
ForegroundColour = Colours.DarkBlue,
Font = MiyagiResources.Singleton.Fonts["SpacedockStencilSmall"]
}
};
DropDownList fsaaDropdown = new DropDownList("optionsFsaaDropdown")
{
Size = new Size(175, 30),
Location = new Point(20, 450),
Skin = MiyagiResources.Singleton.Skins["DropDownListRedSkin"],
DropDownSize = new Size(175, 140),
TextureFiltering = TextureFiltering.Anisotropic,
ListStyle =
{
ItemOffset = new Point(10, 0),
Alignment = Alignment.MiddleLeft,
MaxVisibleItems = 4,
Font = MiyagiResources.Singleton.Fonts["SpacedockStencilSmall"],
MultiSelect = false,
ScrollBarStyle =
{
Extent = 15,
ThumbStyle =
{
BorderStyle =
{
Thickness = new Thickness(2, 2, 2, 2)
}
}
},
},
TextStyle =
{
Offset = new Point(10, 0),
Alignment = Alignment.MiddleLeft,
ForegroundColour = Colours.DarkBlue,
Font = MiyagiResources.Singleton.Fonts["SpacedockStencilSmall"]
}
};
Label fsaaDropdownLabel = new Label("optionsFsaaDropdownLabel")
{
Location = new Point(fsaaDropdown.Location.X + fsaaDropdown.Size.Width + 10, fsaaDropdown.Location.Y + (fsaaDropdown.Size.Height / 4)),
Text = "FSAA",
AutoSize = true,
TextStyle =
{
Font = MiyagiResources.Singleton.Fonts["SpacedockStencilSmall"]
}
};
string[] fsaaOptions = { "0", "2", "4", "8", "16"};
fsaaDropdown.Items.AddRange(fsaaOptions);
string[] selectedOpt = { mEngine.Window.FSAA.ToString() };
fsaaDropdown.SelectItems(selectedOpt);
fsaaDropdown.SelectedIndexChanged += new EventHandler(fsaaDropdown_SelectedIndexChanged);
returnButton.MouseDown += ReturnButtonMouseDown;
applyButton.MouseDown += ApplyButtonMouseDown;
fullscreenCheckbox.CheckedChanged += fullscreenCheckboxChanged;
fullscreenCheckbox.Checked = ConfigManager.Singleton.Configuration.Fullscreen;
resolutionsDropdown.SelectedIndexChanged += new EventHandler(resolutionsDropdown_SelectedIndexChanged);
resolutionsDropdown.Text = ConfigManager.Singleton.Configuration.DisplayWidth.ToString() + " x " + ConfigManager.Singleton.Configuration.DisplayHeight.ToString();
resolutionsDropdown.Items.AddRange(possible_resolutions.Keys.ToArray<string>());
guiOptionsMenu.Controls.Add(passportPanel);
guiOptionsMenu.Controls.Add(ticketPanel);
guiOptionsMenu.Controls.Add(applyButton);
guiOptionsMenu.Controls.Add(fullscreenCheckbox);
guiOptionsMenu.Controls.Add(fullscreenCheckboxLabel);
guiOptionsMenu.Controls.Add(returnButton);
guiOptionsMenu.Controls.Add(resolutionsDropdown);
guiOptionsMenu.Controls.Add(fsaaDropdown);
guiOptionsMenu.Controls.Add(fsaaDropdownLabel);
}
private Dictionary<string, KeyValuePair<uint, uint>> GetResolutions()
{
List<string> resolutions_raw = new List<string>();
ConfigOptionMap conMap = mEngine.Root.RenderSystem.GetConfigOptions();
foreach (KeyValuePair<string, ConfigOption_NativePtr> pair in conMap)
{
if (pair.Key.Equals("Video Mode"))
{
StringVector resolutions = pair.Value.possibleValues;
foreach (string possible in resolutions)
{
if (possible.Contains("@ 16")) continue;
resolutions_raw.Add(possible);
}
}
}
//This stuff is very ugly but i dont give a shit.
resolutions_raw.Remove("640 x 480 @ 32-bit colour");
resolutions_raw.Remove("720 x 480 @ 32-bit colour");
resolutions_raw.Remove("720 x 576 @ 32-bit colour");
resolutions_raw.Remove("800 x 600 @ 32-bit colour");
Dictionary<string, KeyValuePair<uint, uint>> res_lookup = new Dictionary<string, KeyValuePair<uint, uint>>();
foreach (string res in resolutions_raw)
{
string [] build;
build = res.Split(new Char[] { ' ' });
string refined = build[0] + " x " + build[2];
res_lookup.Add(refined, new KeyValuePair<uint, uint>(uint.Parse(build[0]), uint.Parse(build[2])));
}
return res_lookup;
}
private void ApplyButtonMouseDown(object sender, MouseButtonEventArgs e)
{
DropDownList DDFsaa = (DropDownList)mEngine.mMiyagiSystem.GUIManager.GetControl("optionsFsaaDropdown");
ConfigManager.Singleton.Configuration.FSAA = Convert.ToInt32(DDFsaa.SelectedItem.Text);
CheckBox CBFullscreen = (CheckBox)mEngine.mMiyagiSystem.GUIManager.GetControl("optionsFullscreenCheckbox");
ConfigManager.Singleton.Configuration.Fullscreen = CBFullscreen.Checked;
if (res_heigth != 0 && res_width != 0)
{
ConfigManager.Singleton.Configuration.DisplayWidth = res_width;
ConfigManager.Singleton.Configuration.DisplayHeight = res_heigth;
}
ConfigManager.Singleton.Save();
if (changed)
{
DialogResult result; //Where DO THE GRAPHICS FOR THIS STUFF COME FROM. FUCK.
result = DialogBox.Show("Some of the changes require a restart."+Environment.NewLine+"Would you like to restart now?", "Restart?", DialogBoxButtons.YesNo);
if (result == DialogResult.Yes)
{
mEngine.Window.Destroy(); //This is really fucking ugly and unstable. But i really have no better idea.
System.Windows.Forms.Application.Restart();
}
}
}
void resolutionsDropdown_SelectedIndexChanged(object sender, EventArgs e)
{
KeyValuePair<uint, uint> picked_res;
DropDownList ddList = (DropDownList)sender;
if (!possible_resolutions.TryGetValue(ddList.SelectedItem.Text, out picked_res)) mEngine.Window.Destroy();
res_width = picked_res.Key;
res_heigth = picked_res.Value;
changed = true;
}
void fsaaDropdown_SelectedIndexChanged(object sender, EventArgs e)
{
changed = true;
}
private void fullscreenCheckboxChanged(object sender, EventArgs e)
{
changed = true;
}
private void ReturnButtonMouseDown(object sender, MouseButtonEventArgs e)
{
mStateMgr.RequestStateChange(typeof(MainMenu));
}
public override void Shutdown()
{
mEngine.mMiyagiSystem.GUIManager.GetGUI("guiOptionsMenu").Fade(1, 0, 100);
}
public override void Update(long _frameTime)
{
}
#endregion
#region Input
public override void UpdateInput(Mogre.FrameEvent evt, MOIS.Keyboard keyState, MOIS.Mouse mouseState)
{
}
public override void KeyDown(MOIS.KeyEvent keyState)
{
}
public override void KeyUp(MOIS.KeyEvent keyState)
{
}
public override void MouseUp(MOIS.MouseEvent mouseState, MOIS.MouseButtonID button)
{
}
public override void MouseDown(MOIS.MouseEvent mouseState, MOIS.MouseButtonID button)
{
}
public override void MouseMove(MOIS.MouseEvent mouseState)
{
}
#endregion
}
}

3
SS3D_Client/app.config Normal file
View File

@@ -0,0 +1,3 @@
<?xml version="1.0"?>
<configuration>
<startup><supportedRuntime version="v2.0.50727"/></startup></configuration>

View File

@@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Mogre;
namespace SS3D_shared
{
public class AtomBaseClass //This class is the base class for all other objects in the game.
//Think of it as byonds ATOM. Add shared variables here.
//This should always be in the entities UserObject. So make sure to set it.
{
public AtomType AtomType = AtomType.None;
public SceneNode Node;
public Entity Entity;
public string name;
}
}

11
SS3D_shared/Class1.cs Normal file
View File

@@ -0,0 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace SS3D_shared
{
public class Class1
{
}
}

View File

@@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Mogre;
namespace SS3D_shared.HelperClasses
{
public struct InterpolationPacket
{
public double time;
public Mogre.Vector3 position;
public float rotW;
public float rotY;
public InterpolationPacket(Mogre.Vector3 _position, float _rotW, float _rotY, double _time)
{
this.position = _position;
this.rotW = _rotW;
this.rotY = _rotY;
this.time = _time;
}
public InterpolationPacket(float x, float y, float z, float _rotW, float _rotY, double _time)
{
this.position = new Mogre.Vector3(x, y, z);
this.rotW = _rotW;
this.rotY = _rotY;
this.time = _time;
}
}
}

File diff suppressed because it is too large Load Diff

17
SS3D_shared/Mobs/Mob.cs Normal file
View File

@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SS3D_shared.HelperClasses;
using Mogre;
namespace SS3D_shared
{
public abstract class Mob : AtomBaseClass
{
public ushort mobID = 0;
public List<InterpolationPacket> interpolationPacket;
public ServerItemInfo serverInfo;
public AnimationState animState;
}
}

Some files were not shown because too many files have changed in this diff Show More