freeso/TSOClient/tso.simantics/NetPlay/Drivers/VMClientDriver.cs

264 lines
8.8 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using FSO.SimAntics.NetPlay.Model;
using System.Diagnostics;
namespace FSO.SimAntics.NetPlay.Drivers
{
public delegate void OnStateChangeDelegate(int state, float progress);
public class VMClientDriver : VMNetDriver
{
private Queue<VMNetTick> TickBuffer;
private Queue<VMNetCommandBodyAbstract> OutgoingCommands;
private Queue<VMNetMessage> ServerMessages;
private const int TICKS_PER_PACKET = 4;
private const int BUFFER_STABLE_TICKS = 3 * 30; //if buffer does not drop below 2 large for this number of ticks, tighten buffer size
private int BufferSize = TICKS_PER_PACKET * 2;
private int TicksSinceCloseCall = 0;
private bool ReplenishBuffer = false; // when true, ticks run at half speed until BufferSize.
private bool ExecutedAnything;
private int DriverTickPhase = 0;
private int TicksSinceLastCommand;
private VM VMHook; //should probably always backreference the VM anyways, but just used by disconnect
//todo: clean up everything in all of these classes.
/// <summary>
/// Fired when the VM client wishes to send an event to the server.
/// </summary>
public event VMClientCommandHandler OnClientCommand;
public delegate void VMClientCommandHandler(byte[] data); //type is obviously command.
public event OnStateChangeDelegate OnStateChange;
public VMClientDriver(OnStateChangeDelegate callback)
{
OutgoingCommands = new Queue<VMNetCommandBodyAbstract>();
ServerMessages = new Queue<VMNetMessage>();
OnStateChange += callback;
GlobalLink = null; //transactions only performed by server. transaction results
//are passed back to the clients as commands (for the primitive, at least)
TickBuffer = new Queue<VMNetTick>();
}
public void Connected()
{
if (OnStateChange != null) OnStateChange(1, 0f);
}
public override void SendCommand(VMNetCommandBodyAbstract cmd)
{
OutgoingCommands.Enqueue(cmd);
}
private void SendToServer(VMNetCommandBodyAbstract cmd)
{
byte[] data;
using (var stream = new MemoryStream())
{
var cmd2 = new VMNetCommand(cmd);
using (var writer = new BinaryWriter(stream))
{
cmd2.SerializeInto(writer);
}
data = stream.ToArray();
}
if (OnClientCommand != null) OnClientCommand(data);
}
public override bool Tick(VM vm)
{
VMHook = vm;
DriverTickPhase++;
HandleNet(); //handle messages queued by external networking
if (OnClientCommand != null)
{
while (OutgoingCommands.Count > 0)
{
SendToServer(OutgoingCommands.Dequeue());
}
}
var timer = new Stopwatch();
timer.Start();
int tickSpeed;
// === BUFFER SIZE MANAGEMENT (reduces stutter) ===
if (TickBuffer.Count > BufferSize * 3)
tickSpeed = 2000;
else if (TickBuffer.Count > BufferSize * 2)
tickSpeed = 2;
else tickSpeed = 1;
TicksSinceLastCommand++;
if (TickBuffer.Count == 0)
{
tickSpeed = 0;
if (!ReplenishBuffer && ExecutedAnything)
{
BufferSize++;
ReplenishBuffer = true;
}
if (TicksSinceLastCommand > 45)
{
SendCommand(new Model.Commands.VMNetPingCmd());
TicksSinceLastCommand = -30*30;
}
}
else
{
TicksSinceLastCommand = Math.Min(TicksSinceLastCommand, 0);
if (TickBuffer.Count <= TICKS_PER_PACKET) TicksSinceCloseCall = 0;
}
if (ReplenishBuffer)
{
if (TickBuffer.Count >= BufferSize) ReplenishBuffer = false;
else if (DriverTickPhase % 2 == 0) tickSpeed = 0; //run at half speed til buffer replenished
}
else if (TicksSinceCloseCall++ > BUFFER_STABLE_TICKS)
{
TicksSinceCloseCall = 0;
BufferSize--;
if (BufferSize < TICKS_PER_PACKET) BufferSize = TICKS_PER_PACKET;
}
// === END BUFFER SIZE MANAGEMENT ===
for (int i = 0; i < tickSpeed && TickBuffer.Count > 0; i++)
{
ExecutedAnything = true;
var tick = TickBuffer.Dequeue();
InternalTick(vm, tick);
if (vm.FSOVAsyncLoading)
{
//we ran the load async, but not anything else
//right now we assume the sync tick is by itself, and sets "runTick" to false anyways
//so it does not need to be requeued
/* requeue code
var temp = new List<VMNetTick>(TickBuffer);
temp.Insert(0, tick);
TickBuffer = new Queue<VMNetTick>(temp);
*/
return false;
}
if (timer.ElapsedMilliseconds > 66)
{
timer.Stop();
if (TickBuffer.Count > 30 || !vm.Context.Ready)
{
OnStateChange(2, tick.TickID / (float)(tick.TickID + TickBuffer.Count));
return false;
}
else if(!vm.Ready)
{
OnStateChange(3, 0f);
}
return true;
}
}
if (!vm.Ready)
{
if (ExecutedAnything && vm.Context.Ready)
{
OnStateChange(3, 0f);
return true;
}
else return false;
}
return true;
}
public void ServerMessage(VMNetMessage message)
{
lock (ServerMessages)
{
ServerMessages.Enqueue(message);
}
}
private void HandleNet()
{
lock (ServerMessages)
{
var packets = ServerMessages;
while (packets.Count > 0)
{
HandleServerMessage(packets.Dequeue());
}
}
}
private void HandleServerMessage(VMNetMessage message)
{
if (message.Type == VMNetMessageType.Direct)
{
var cmd = new VMNetCommand();
try
{
using (var reader = new BinaryReader(new MemoryStream(message.Data)))
{
cmd.Deserialize(reader);
}
cmd.Command.Execute(VMHook, VMHook.GetAvatarByPersist(cmd.Command.ActorUID));
}
catch (Exception e)
{
CloseReason = VMCloseNetReason.NetExceptionDirect;
CloseString = "An exception occurred while running a direct tick from the server, so the lot connection was terminated: \n\n" + e.ToString();
Shutdown();
return;
}
}
else
{
var tick = new VMNetTickList();
try
{
using (var reader = new BinaryReader(new MemoryStream(message.Data)))
{
tick.Deserialize(reader);
}
}
catch (Exception e)
{
CloseReason = VMCloseNetReason.NetException;
CloseString = "An exception occurred while running a broadcast tick from the server, so the lot connection was terminated: \n\n" + e.ToString();
Shutdown();
return;
}
for (int i = 0; i < tick.Ticks.Count; i++)
{
tick.Ticks[i].ImmediateMode = tick.ImmediateMode;
TickBuffer.Enqueue(tick.Ticks[i]);
}
}
}
public override string GetUserIP(uint uid)
{
return "remote";
}
public override void SendDirectCommand(uint pid, VMNetCommandBodyAbstract cmd)
{
//should not be used by client.
throw new NotImplementedException();
}
}
}