mirror of
https://github.com/riperiperi/FreeSO
synced 2026-08-13 20:26:13 -04:00
264 lines
8.8 KiB
C#
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();
|
|
}
|
|
}
|
|
}
|