using BNEBotCore;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace BNEBotLauncher
{
public static class BotProcessManager
{
static BotProcessManager()
{
m_UpdateThread = new Thread(UpdateProc);
m_UpdateThread.Start();
}
// Launch the given No. of bots
public static void SpawnBots(int count, string strategy = "", int targetLevel = 0)
{
for (int i = 0; i < count; ++i)
{
try
{
string botExe = LaunchSettings.ExeFile;
if (LaunchSettings.DebugExe)
{
botExe += "_d";
}
BotProcess bot = BotProcess.StartNew(botExe, strategy, targetLevel);
lock (m_DataLock)
{
m_RunningBots.Add(bot);
}
Thread.Sleep(LaunchSettings.SpawnInterval);
}
catch (Exception)
{
// TODO: error output
i = i;
}
}
}
// Enqueue spawn command
public static void QueueSpawnCommand(SpawnBotsCommand.SpawnInfo spawnInfo)
{
EnqueuedSpawns.Enqueue(spawnInfo);
}
private static ConcurrentQueue<SpawnBotsCommand.SpawnInfo> EnqueuedSpawns = new ConcurrentQueue<SpawnBotsCommand.SpawnInfo>();
// Terminate all bot processes
public static void KillAll()
{
lock (m_DataLock)
{
foreach (BotProcess bot in m_RunningBots)
{
bot.Kill();
}
}
}
// Similar to KillAll but indicates normal exit path
// Returns true if all the bots have exited normally (not crashed)
public static bool Terminate()
{
bool result = true;
lock (m_DataLock)
{
// Collect some stats
int running = 0, stopped = 0, killed = 0, crashed = 0;
foreach (BotProcess bot in m_RunningBots)
{
if (bot.Bot.State == BotState.Running)
{
running++;
bot.Bot.State = BotState.Stopped;
bot.Kill();
}
else if (bot.Bot.State == BotState.Crashed)
{
crashed++;
result = false;
}
else
{
switch (bot.Bot.State)
{
case BotState.Crashed:
crashed++;
break;
case BotState.Killed:
killed++;
break;
case BotState.Running:
running++;
break;
case BotState.Stopped:
stopped++;
break;
}
}
}
Console.WriteLine("Bot launcher finished, report:");
Console.WriteLine("\tBots finished: " + stopped);
Console.WriteLine("\tBots crashed: " + crashed);
Console.WriteLine("\tBots killed: " + killed);
Console.WriteLine("\tBots still running: " + running);
}
m_Exit = true;
if (m_UpdateThread.IsAlive)
{
m_UpdateThread.Join();
}
return result;
}
// Terminate a specific bot process
public static void KillBot(string botName)
{
lock (m_DataLock)
{
foreach (BotProcess bot in m_RunningBots)
{
if (bot.Bot.Name == botName)
{
bot.Kill();
break;
}
}
}
}
// Collect info about all bots
public static List<BotInfo> GetBotInfo()
{
lock (m_DataLock)
{
// FIXME: we also use this to update the process info
List<BotInfo> bots = new List<BotInfo>();
foreach (BotProcess bot in m_RunningBots)
{
if (!bot.Proc.HasExited)
{
bot.Bot.Uptime = DateTime.Now - bot.Bot.LaunchTimestamp;
}
bots.Add(bot.Bot);
}
//return (from b in m_RunningBots select b.Bot).ToList();
return bots;
}
}
// Check if all the bots have finished
public static bool AnyBotRunning()
{
lock (m_DataLock)
{
foreach (BotProcess bot in m_RunningBots)
{
if (!bot.Proc.HasExited)
{
return true;
}
}
return false;
}
}
// Update bot data
private static void UpdateProc()
{
if (LaunchSettings.Randomize)
{
PrepareNextRandomEvent();
}
while (!m_Exit)
{
// FIXME: for now we just use this thread to spawn queued bots. This will hang it until
// the spawn finishes to no logic further down will run for a while. This is no problem
// now as we have disabled the perf stats tracking anyway...
SpawnBotsCommand.SpawnInfo spawn;
if (EnqueuedSpawns.TryDequeue(out spawn))
{
Stopwatch sw = Stopwatch.StartNew();
SpawnBots(spawn.NumBots, spawn.Strategy, spawn.TargetLevel);
if (LaunchSettings.Randomize)
{
// Make sure the time spent spawning does not interfere with our randomization timers
m_NextRandomEvent += sw.Elapsed;
}
}
if (LaunchSettings.Randomize)
{
if (m_NextRandomEvent <= DateTime.Now)
{
// Count how many bots are still running
List<BotProcess> stopped = new List<BotProcess>();
foreach (BotProcess bot in m_RunningBots)
{
if (bot.Bot.State != BotState.Running)
{
stopped.Add(bot);
}
}
// How many will be affected
int cnt = LaunchSettings.RandomCountMin + m_Random.Next(LaunchSettings.RandomCountMax - LaunchSettings.RandomCountMin);
cnt = Math.Min(cnt, m_RunningBots.Count);
if (m_RandomKill)
{
// Need to kill some more
int tokill = cnt - stopped.Count;
while (tokill > 0)
{
int bot = m_Random.Next(m_RunningBots.Count);
BotProcess proc = m_RunningBots[bot];
if (proc.Bot.State == BotState.Running)
{
tokill--;
proc.Kill();
}
}
}
else
{
// Restart stopped bots
// TEST: restart them all
foreach (BotProcess bot in stopped)
{
bot.Restart();
Thread.Sleep(LaunchSettings.SpawnInterval);
}
}
// Prepare a new one
m_RandomKill = !m_RandomKill;
PrepareNextRandomEvent();
}
}
//int i = 0;
//int count = 0;
//lock (m_DataLock)
//{
// count = m_RunningBots.Count;
//}
//for (; i < count; ++i)
//{
// lock (m_DataLock)
// {
// if (i < m_RunningBots.Count)
// {
// m_RunningBots[i].UpdatePerfStats();
// }
// }
//}
Thread.Sleep(10);
}
}
private static void PrepareNextRandomEvent()
{
m_NextRandomEvent = DateTime.Now;
double min = LaunchSettings.RandomIntervalMin.TotalMinutes;
double interval = LaunchSettings.RandomIntervalMax.TotalMinutes - min;
m_NextRandomEvent = m_NextRandomEvent.AddMinutes(min + (m_Random.NextDouble() * interval));
}
// Running bots
private static List<BotProcess> m_RunningBots = new List<BotProcess>();
private static Thread m_UpdateThread;
private static bool m_Exit;
private static object m_DataLock = new object();
// Spawn randomization
private static Random m_Random = new Random();
private static DateTime m_NextRandomEvent;
private static bool m_RandomKill = true;
}
}