Files
OpenRA/OpenRa.Game/OrderManager.cs
2009-12-02 07:50:57 +13:00

265 lines
6.7 KiB
C#
Executable File

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Sockets;
using System.Threading;
namespace OpenRa.Game
{
class OrderManager
{
Stream savingReplay;
List<OrderSource> sources;
int frameNumber = 0;
const int FramesAhead = 3;
public bool GameStarted { get { return frameNumber != 0; } }
public bool IsNetplay { get { return sources.OfType<NetworkOrderSource>().Any(); } }
public void StartGame()
{
if (GameStarted) return;
frameNumber = 1;
foreach (var p in this.sources)
for (int i = frameNumber; i <= FramesAhead; i++)
p.SendLocalOrders(i, new List<Order>());
}
public int FrameNumber { get { return frameNumber; } }
public OrderManager( IEnumerable<OrderSource> sources )
{
this.sources = sources.ToList();
if (!IsNetplay)
StartGame();
}
public OrderManager( IEnumerable<OrderSource> sources, string replayFilename )
: this( sources )
{
savingReplay = new FileStream( replayFilename, FileMode.Create );
}
public bool IsReadyForNextFrame
{
get
{
foreach( var p in sources )
if( !p.IsReadyForFrame( frameNumber ) )
return false;
return true;
}
}
public void TickImmediate()
{
var localOrders = Game.controller.GetRecentOrders(true);
if (localOrders.Count > 0)
foreach (var p in sources)
p.SendLocalOrders(0, localOrders);
var immOrders = sources.SelectMany( p => p.OrdersForFrame(0) ).OrderBy(o => o.Player.Index).ToList();
foreach (var order in immOrders)
UnitOrders.ProcessOrder(order);
}
public void Tick()
{
var localOrders = Game.controller.GetRecentOrders(false);
foreach( var p in sources )
p.SendLocalOrders( frameNumber + FramesAhead, localOrders );
var allOrders = sources.SelectMany(p => p.OrdersForFrame(frameNumber)).OrderBy(o => o.Player.Index).ToList();
foreach (var order in allOrders)
UnitOrders.ProcessOrder(order);
if( savingReplay != null )
savingReplay.WriteFrameData( allOrders, frameNumber );
++frameNumber;
// sanity check on the framenumber. This is 2^31 frames maximum, or multiple *years* at 40ms/frame.
if( ( frameNumber & 0x80000000 ) != 0 )
throw new InvalidOperationException( "(OrderManager) Frame number too large" );
}
}
interface OrderSource
{
void SendLocalOrders( int localFrame, List<Order> localOrders );
List<Order> OrdersForFrame( int currentFrame );
bool IsReadyForFrame( int frameNumber );
}
class LocalOrderSource : OrderSource
{
Dictionary<int, List<Order>> orders = new Dictionary<int,List<Order>>();
public List<Order> OrdersForFrame( int currentFrame )
{
// TODO: prune `orders` based on currentFrame.
if (!orders.ContainsKey(currentFrame))
return new List<Order>();
return orders[ currentFrame ];
}
public void SendLocalOrders( int localFrame, List<Order> localOrders )
{
if (localFrame == 0) return;
orders[ localFrame ] = localOrders;
}
public bool IsReadyForFrame( int frameNumber )
{
return true;
}
}
class ReplayOrderSource : OrderSource
{
BinaryReader replayReader;
public ReplayOrderSource( string replayFilename )
{
replayReader = new BinaryReader( File.Open( replayFilename, FileMode.Open ) );
}
public void SendLocalOrders( int localFrame, List<Order> localOrders ) { }
public List<Order> OrdersForFrame( int frameNumber )
{
try
{
var len = replayReader.ReadInt32() - 4;
var frame = replayReader.ReadInt32();
var ret = replayReader.ReadBytes( len ).ToOrderList();
if( frameNumber != frame )
throw new InvalidOperationException( "Attempted time-travel in OrdersForFrame (replay)" );
return ret;
}
catch( EndOfStreamException )
{
return new List<Order>();
}
}
public bool IsReadyForFrame( int frameNumber )
{
return true;
}
}
class NetworkOrderSource : OrderSource
{
TcpClient socket;
Dictionary<int, List<byte[]>> orderBuffers = new Dictionary<int, List<byte[]>>();
Dictionary<int, bool> gotEverything = new Dictionary<int, bool>();
public NetworkOrderSource( TcpClient socket )
{
this.socket = socket;
this.socket.NoDelay = true;
var reader = new BinaryReader( socket.GetStream() );
new Thread( () =>
{
for (; ; )
{
var len = reader.ReadInt32();
var frame = reader.ReadInt32();
var buf = reader.ReadBytes(len - 4);
lock (orderBuffers)
{
if (len == 5 && buf[0] == 0xef) /* got everything marker */
gotEverything[frame] = true;
else
{
/* accumulate this chunk */
if (!orderBuffers.ContainsKey(frame))
orderBuffers[frame] = new List<byte[]> { buf };
else
orderBuffers[frame].Add(buf);
}
}
}
} ) { IsBackground = true }.Start();
}
static List<byte[]> NoOrders = new List<byte[]>();
List<byte[]> ExtractOrders(int frame)
{
lock (orderBuffers)
{
List<byte[]> result;
if (!orderBuffers.TryGetValue(frame, out result))
result = NoOrders;
orderBuffers.Remove(frame);
gotEverything.Remove(frame);
return result;
}
}
public List<Order> OrdersForFrame( int currentFrame )
{
var orderData = ExtractOrders(currentFrame);
return orderData.SelectMany(a => a.ToOrderList()).ToList();
}
public void SendLocalOrders( int localFrame, List<Order> localOrders )
{
socket.GetStream().WriteFrameData( localOrders, localFrame );
}
public bool IsReadyForFrame( int frameNumber )
{
lock( orderBuffers )
return gotEverything.ContainsKey( frameNumber );
}
}
static class StreamExts
{
public static void Write( this Stream s, byte[] buf )
{
s.Write( buf, 0, buf.Length );
}
}
static class OrderIO
{
public static MemoryStream ToMemoryStream( this IEnumerable<Order> orders, int nextLocalOrderFrame )
{
var ms = new MemoryStream();
ms.Write( BitConverter.GetBytes( nextLocalOrderFrame ) );
foreach( var order in orders )
ms.Write( order.Serialize() );
return ms;
}
public static void WriteFrameData( this Stream s, IEnumerable<Order> orders, int frameNumber )
{
var ms = orders.ToMemoryStream( frameNumber );
s.Write( BitConverter.GetBytes( (int)ms.Length ) );
ms.WriteTo( s );
}
public static List<Order> ToOrderList( this byte[] bytes )
{
var ms = new MemoryStream( bytes );
var reader = new BinaryReader( ms );
var ret = new List<Order>();
while( ms.Position < ms.Length )
ret.Add( Order.Deserialize( reader ) );
return ret;
}
}
}