Newer
Older
Import / projects / Gameloft / bne_lib / tools / BNEBotManager / LauncherConnection.cs
using BNEBotCore;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;

namespace BNEBotManager
{
  public class LauncherConnection : ViewModelBase
  {
    public LauncherConnection(IPAddress addr, int port)
    {
      Bots = new ObservableCollection<BotInfo>();
      SelectedBots = new ObservableCollection<BotInfo>();
      SelectedBots.CollectionChanged += (s, e) =>
      {
        HasSelectedBots = Bots.Count > 0;
      };
      m_LastIncomingMessage = DateTime.Now;
      RemoteEndpoint = new IPEndPoint(addr, port);

      // Default name
      ID = RemoteEndpoint.ToString();

      m_UpdateThread = new Thread(UpdateProc);
      m_UpdateThread.Start();
    }

    public string ID
    {
      get;
      set;
    }

    public bool Connected
    {
      get { return m_Connected; }
      private set
      {
        m_Connected = value;
        OnPropertyChanged();
        OnPropertyChanged("Disconnected");
      }
    }
    private bool m_Connected;

    public bool Disconnected
    {
      get { return !m_Connected; }
    }

    public IPEndPoint RemoteEndpoint
    {
      get;
      private set;
    }

    public TcpClient Client
    {
      get;
      private set;
    }

    public NetworkStream Stream
    {
      get;
      private set;
    }

    public BinaryWriter Writer
    {
      get;
      private set;
    }

    public BinaryReader Reader
    {
      get;
      private set;
    }

    public ObservableCollection<BotInfo> Bots
    {
      get;
      private set;
    }

    public ObservableCollection<BotInfo> SelectedBots
    {
      get;
      private set;
    }

    public bool HasSelectedBots
    {
      get { return m_HasRunningBots; }
      set
      {
        m_HasRunningBots = value;
        OnPropertyChanged();
      }
    }
    private bool m_HasRunningBots;

    public int RunningBotCount
    {
      get { return m_RunningBotCount; }
      set
      {
        m_RunningBotCount = value;
        OnPropertyChanged();
      }
    }
    private int m_RunningBotCount;

    public int FinishedBotCount
    {
      get { return m_FinishedBotCount; }
      set
      {
        m_FinishedBotCount = value;
        OnPropertyChanged();
      }
    }
    private int m_FinishedBotCount;

    public int KilledBotCount
    {
      get { return m_KilledBotCount; }
      set
      {
        m_KilledBotCount = value;
        OnPropertyChanged();
      }
    }
    private int m_KilledBotCount;

    public int CrashedBotCount
    {
      get { return m_CrashedBotCount; }
      set
      {
        m_CrashedBotCount = value;
        OnPropertyChanged();
      }
    }
    private int m_CrashedBotCount;

    public int BotSpawnCount
    {
      get { return m_BotSpawnCount; }
      set
      {
        m_BotSpawnCount = value;
        OnPropertyChanged();
      }
    }
    private int m_BotSpawnCount = 16;

    public int BotSpawnTarget
    {
      get { return m_BotSpawnTarget; }
      set
      {
        m_BotSpawnTarget = value;
        OnPropertyChanged();
      }
    }
    private int m_BotSpawnTarget = 10;

    public string BotSpawnStrategy
    {
      get { return m_BotSpawnStrategy; }
      set
      {
        m_BotSpawnStrategy = value;
        OnPropertyChanged();
      }
    }
    private string m_BotSpawnStrategy = "cheater";

    public List<string> AvailableBotStrategies
    {
      get { return m_AvailableBotStrategies; }
    }
    private List<string> m_AvailableBotStrategies = new List<string>()
    {
      "slow",
      "free",
      "builder",
      "updater",
      "cheater"
    };

    public ICommand KillSelectedBots
    {
      get
      {
        return m_KillSelectedBots ?? (m_KillSelectedBots = new CommandHandler(() => KillBots(SelectedBots)));
      }
    }
    private ICommand m_KillSelectedBots;

    public ICommand KillAllBots
    {
      get
      {
        return m_KillAllBots ?? (m_KillAllBots = new CommandHandler(() => KillBots(Bots)));
      }
    }
    private ICommand m_KillAllBots;

    public ICommand TerminateServer
    {
      get
      {
        return m_TerminateServer ?? (m_TerminateServer = new CommandHandler(() => OnTerminateServer()));
      }
    }
    private ICommand m_TerminateServer;

    public ICommand SpawnBots
    {
      get
      {
        return m_SpawnBots ?? (m_SpawnBots = new CommandHandler(() => OnSpawnBots()));
      }
    }
    private ICommand m_SpawnBots;

    public void Close()
    {
      m_Exit = true;
      //m_UpdateThread.Join();
      m_UpdateThread.Abort();
    }

    public void KillBots(IList<BotInfo> bots)
    {
      List<string> botNames = new List<string>();
      KillBotCommand cmd = new KillBotCommand()
      {
        BotsToKill = botNames
      };

      // TEST: optimization: if all bots are selected, just send an empty list
      if (bots.Count != Bots.Count)
      {
        foreach (BotInfo bot in bots)
        {
          botNames.Add(bot.Name);
        }
      }

      cmd.Finalize();

      // TODO: move this to a shared location
      Writer.Write((int)cmd.Type);
      if (cmd.CompressedData == null)
      {
        Writer.Write(0);
      }
      else
      {
        Writer.Write(cmd.CompressedData.Length);
        Writer.Write(cmd.CompressedData);
      }
    }

    public void OnTerminateServer()
    {
      TerminateServerCommand cmd = new TerminateServerCommand();
      cmd.Finalize();

      // TODO: move this to a shared location
      Writer.Write((int)cmd.Type);
      if (cmd.CompressedData == null)
      {
        Writer.Write(0);
      }
      else
      {
        Writer.Write(cmd.CompressedData.Length);
        Writer.Write(cmd.CompressedData);
      }
    }

    public void OnSpawnBots()
    {
      SpawnBotsCommand.SpawnInfo info = new SpawnBotsCommand.SpawnInfo()
      {
        NumBots = BotSpawnCount,
        Strategy = BotSpawnStrategy,
        TargetLevel = BotSpawnTarget
      };

      SpawnBotsCommand cmd = new SpawnBotsCommand()
      {
        Info = info
      };

      cmd.Finalize();

      // TODO: move this to a shared location
      Writer.Write((int)cmd.Type);
      if (cmd.CompressedData == null)
      {
        Writer.Write(0);
      }
      else
      {
        Writer.Write(cmd.CompressedData.Length);
        Writer.Write(cmd.CompressedData);
      }
    }

    async private void AttemptConnection()
    {
      Client = new TcpClient();

      try
      {
        await Client.ConnectAsync(RemoteEndpoint.Address, RemoteEndpoint.Port);

        // Success!
        await Application.Current.Dispatcher.BeginInvoke(new Action(() => { Connected = true; }));

        Stream = Client.GetStream();
        Writer = new BinaryWriter(Stream);
        Reader = new BinaryReader(Stream);
      }
      catch (Exception)
      {
        // Cannot connect yet...
      }
    }

    private void UpdateProc()
    {
      while (!m_Exit)
      {
        if (Connected)
        {
          if (Client.Available > 0)
          {
            // We've got a message from the launcher
            m_LastIncomingMessage = DateTime.Now;

            NetCommandType type = (NetCommandType)Reader.ReadInt32();
            int dataSize = Reader.ReadInt32();
            if (dataSize > 0)
            {
              byte[] data = Reader.ReadBytes(dataSize);

              switch (type)
              {
                case NetCommandType.BotInfo:
                  OnBotInfoCommand(data);
                  break;
              }
            }
          }
          else
          {
            // Check for disconnect
            if ((DateTime.Now - m_LastIncomingMessage).TotalSeconds > 5)
            {
              // Let's try to send few bytes to the server. If the call fails, we know it has disconnected
              try
              {
                Writer.Write((int)NetCommandType.Ping);
                Writer.Write(0);

                m_LastIncomingMessage = DateTime.Now;
              }
              catch (Exception)
              {
                // Clear data
                Application.Current.Dispatcher.BeginInvoke(new Action(() =>
                {
                  Reset();
                }));
              }
            }
          }

          Thread.Sleep(100);
        }
        else
        {
          // Try reconnecting
          AttemptConnection();

          if (!Connected)
          {
            Thread.Sleep(5000);
          }
        }
      }
    }

    private void OnBotInfoCommand(byte[] data)
    {
      BotInfoCommand cmd = new BotInfoCommand()
      {
        CompressedData = data
      };
      cmd.Restore();

      if (cmd.UpdatedBots != null)
      {
        Application.Current.Dispatcher.BeginInvoke(new Action(() =>
        {
          // TEST: we never remove bots, we always keep the record, unless it is replaced by a bot with the same name
          foreach (BotInfo bot in cmd.UpdatedBots)
          {
            // Find an existing bot
            BotInfo existingBot = Bots.FirstOrDefault((b) => { return b.Name == bot.Name; });
            if (existingBot == null)
            {
              // New one
              Bots.Add(bot);
            }
            else
            {
              // Update
              existingBot.ForceState(bot.State);
              existingBot.CPUUsage = bot.CPUUsage;
              existingBot.MemoryUsage = bot.MemoryUsage;
              existingBot.LaunchTimestamp = bot.LaunchTimestamp;
              existingBot.Uptime = bot.Uptime;
            }
          }

          // Update stats
          int running = 0, finished = 0, crashed = 0, killed = 0;
          foreach (BotInfo bot in Bots)
          {
            switch (bot.State)
            {
              case BotState.Running:
                running++;
                break;
              case BotState.Stopped:
                finished++;
                break;
              case BotState.Killed:
                killed++;
                break;
              case BotState.Crashed:
                crashed++;
                break;
            }
          }

          RunningBotCount = running;
          FinishedBotCount = finished;
          CrashedBotCount = crashed;
          KilledBotCount = killed;

        }));
      }
    }

    private void Reset()
    {
      Bots.Clear();
      RunningBotCount = FinishedBotCount = CrashedBotCount = KilledBotCount = 0;

      Connected = false;
      //Client = null;
      Stream = null;
      Writer = null;
      Reader = null;
    }

    private Thread m_UpdateThread;
    private bool m_Exit;
    private DateTime m_LastIncomingMessage;
  }
}