Hi there friends,
In writing my Holy PvE CC, I found that reloading the CombatRoutine was very tedious - had to kill HB, login again, wait for InstanceBuddy to 'check for updates'.
What is it
What I've created here is a way for CCs to be auto-reloading. It is not the only way to do this, nor do I claim it is the best, but it's certainly a start.
Essentially, the way this is achieved is by creating a second CC that acts as a wrapper for the CC you wish to be reloadable. In my implementation, this is done by:
The default interval for checking is 10 seconds - it's a very cheap operation. Just takes the last write date on files. Change it if you like.
If your CC is very procedural or dependent on a lot of variables, you need to remember that these will all be WIPED when the object is swapped out. My CCs are more based upon checking states and responding to them, which is why this is possible. You have been warned.
This creates an extra thread (and CC) for every CC you are 'auto-reloading'. You can do this without a thread by moving all of the reloading code to Pulse() and adding a 10 second timer, for example, but that is not how I did this. In any case, this is targeted at developers.
It will only pass .cs, .res, .resx to the Compiler, but you can change this. Passing meaningless files will cause errors.
Before you ask, this won't wipe you if you're healing and it decides to reload. It will either wait for the CombatRoutine methods to stop executing, or it will reload very quickly and allow it to keep executing. I can't imagine any circumstances in which this could freeze.
Before you ask, if a reload fails to compile, it will output the errors and not interrupt the operation of the existing CombatRoutine object.
Before you ask, I wrote this to work on CCs that are comprised of more than one file, though I have not tested it in that capacity.
Operation
When you login to the HonorBuddy client, it compiles all of the CCs. You need to wait for it to do this, and you will get output similar to:
Then, when you start a bot, the CC you need to select will be similar to
After that point, you can simply save any file in the CC folder and it will reload it when opportune (even while executing):
The Code
Obviously you would have to change the namespace, classname, CC_FOLDER, CC_TYPE (which is namespace.classname of the target CC) in order to get this working for a different CC. This is simply a demo of how I am doing it for my Holy PvE CC (which is posted in the Paladin forum).
tldr: CC reloads itself when it detects changes to itself lolz
In writing my Holy PvE CC, I found that reloading the CombatRoutine was very tedious - had to kill HB, login again, wait for InstanceBuddy to 'check for updates'.
What is it
What I've created here is a way for CCs to be auto-reloading. It is not the only way to do this, nor do I claim it is the best, but it's certainly a start.
Essentially, the way this is achieved is by creating a second CC that acts as a wrapper for the CC you wish to be reloadable. In my implementation, this is done by:
- Creating a background thread that scans for file changes in the CC folder
- Upon changes, creates an assembly based on that folder, and derives a CombatRoutine object from it
- When possible (when concurrency allows), swaps out the existing CombatRoutine object for the new one.
The default interval for checking is 10 seconds - it's a very cheap operation. Just takes the last write date on files. Change it if you like.
If your CC is very procedural or dependent on a lot of variables, you need to remember that these will all be WIPED when the object is swapped out. My CCs are more based upon checking states and responding to them, which is why this is possible. You have been warned.
This creates an extra thread (and CC) for every CC you are 'auto-reloading'. You can do this without a thread by moving all of the reloading code to Pulse() and adding a 10 second timer, for example, but that is not how I did this. In any case, this is targeted at developers.
It will only pass .cs, .res, .resx to the Compiler, but you can change this. Passing meaningless files will cause errors.
Before you ask, this won't wipe you if you're healing and it decides to reload. It will either wait for the CombatRoutine methods to stop executing, or it will reload very quickly and allow it to keep executing. I can't imagine any circumstances in which this could freeze.
Before you ask, if a reload fails to compile, it will output the errors and not interrupt the operation of the existing CombatRoutine object.
Before you ask, I wrote this to work on CCs that are comprised of more than one file, though I have not tested it in that capacity.
Operation
When you login to the HonorBuddy client, it compiles all of the CCs. You need to wait for it to do this, and you will get output similar to:
Code:
F:\Users\alex\Documents\hb\CustomClasses\HolyPvE\HolyPvE.cs
Waiting on mutex
[Re]loaded CC
Code:
Loader: HolyPvE.HolyPvE
Code:
F:\Users\alex\Documents\hb\CustomClasses\HolyPvE\HolyPvE.cs has changed
CC needs [re]loading
F:\Users\alex\Documents\hb\CustomClasses\HolyPvE\HolyPvE.cs
Waiting on mutex
[Re]loaded CC
The Code
Obviously you would have to change the namespace, classname, CC_FOLDER, CC_TYPE (which is namespace.classname of the target CC) in order to get this working for a different CC. This is simply a demo of how I am doing it for my Holy PvE CC (which is posted in the Paladin forum).
Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Styx.Combat.CombatRoutine;
using System.Threading;
using System.IO;
using Styx.Helpers;
using Microsoft.CSharp;
using System.CodeDom.Compiler;
using System.Reflection;
using TreeSharp;
namespace HolyPally
{
class HolyPvE_Stub : CombatRoutine
{
private static string CC_FOLDER = Logging.ApplicationPath + "\\CustomClasses\\HolyPvE\\";
private static string CC_TYPE = "HolyPvE.HolyPvE";
private static int RELOAD_INTERVAL = 10;
private Mutex _mu;
private Thread _t;
private CombatRoutine _cc;
private Dictionary<string, DateTime> _edits;
private CSharpCodeProvider _cp;
private CompilerParameters _params;
public HolyPvE_Stub()
{
if (!Directory.Exists(CC_FOLDER))
{
Logging.Write(CC_FOLDER + " does not exist, CC failed to load.");
}
else
{
_edits = new Dictionary<string, DateTime>();
_mu = new Mutex();
// Load the CC once, so we don't get calls to a null _cc.
Reload();
_t = new Thread(new ThreadStart(ReloadWorker));
_t.IsBackground = true;
_t.Start();
}
}
private void ReloadWorker()
{
while (_t.IsAlive)
{
if (NeedsReloading())
{
Logging.Write("CC needs [re]loading");
Reload();
}
Thread.Sleep(new TimeSpan(0, 0, RELOAD_INTERVAL));
}
}
private void Reload()
{
CombatRoutine cr = LoadAssembly();
if (cr != null)
{
Logging.Write("Waiting on mutex");
_mu.WaitOne();
_cc = cr;
_cc.Initialize();
_mu.ReleaseMutex();
Logging.Write("[Re]loaded CC");
}
}
private bool NeedsReloading()
{
bool result = false;
foreach(string s in EnumerateFiles(null))
{
if (!WantInclude(s))
{
continue;
}
if (_edits.ContainsKey(s))
{
if(DateTime.Compare(_edits[s], File.GetLastWriteTime(s)) < 0)
{
Logging.Write(s + " has changed");
_edits[s] = File.GetLastWriteTime(s);
result = true;
}
}
else
{
_edits.Add(s, File.GetLastWriteTime(s));
}
}
return result;
}
private bool WantInclude(string s)
{
foreach(string ext in INCLUDED_EXTENSIONS)
{
if (s.ToLower().EndsWith(ext.ToLower()))
{
return true;
}
}
return false;
}
private CombatRoutine LoadAssembly()
{
if (_cp == null)
{
Dictionary<string, string> opts = new Dictionary<string, string>();
opts.Add("CompilerVersion", "v3.5");
_cp = new CSharpCodeProvider(opts);
}
if (_params == null)
{
_params = new CompilerParameters();
_params.GenerateExecutable = false;
_params.GenerateInMemory = true;
_params.CompilerOptions = "/target:library";
_params.ReferencedAssemblies.AddRange(ASSEMBLIES);
}
List<String> files = EnumerateFiles(null);
CompilerResults result = _cp.CompileAssemblyFromFile(_params, files.ToArray());
if (result.Errors.Count > 0)
{
foreach (CompilerError e in result.Errors)
{
Logging.Write("Compile error: " + e.ToString());
}
return null;
}
else
{
Assembly a = result.CompiledAssembly;
object o = a.CreateInstance(CC_TYPE);
if (o != null && o is CombatRoutine)
{
return (CombatRoutine)o;
}
else
{
Logging.Write("Something went wrong creating the instance");
return null;
}
}
}
private List<string> EnumerateFiles(string path)
{
List<string> files = new List<string>();
if (path == null)
{
path = CC_FOLDER;
}
foreach (string file in Directory.GetFiles(path))
{
files.Add(file);
}
foreach (string dir in Directory.GetDirectories(path))
{
files.AddRange(EnumerateFiles(dir));
}
return files;
}
public override string Name
{
get
{
return "Loader: " + CC_TYPE;
}
}
public override WoWClass Class
{
get
{
_mu.WaitOne();
WoWClass c = _cc.Class;
_mu.ReleaseMutex();
return c;
}
}
public override Composite CombatBehavior
{
get
{
_mu.WaitOne();
Composite c = _cc.CombatBehavior;
_mu.ReleaseMutex();
return c;
}
}
public override Composite CombatBuffBehavior
{
get
{
_mu.WaitOne();
Composite c = _cc.CombatBuffBehavior;
_mu.ReleaseMutex();
return c;
}
}
public override Composite HealBehavior
{
get
{
_mu.WaitOne();
Composite c = _cc.HealBehavior;
_mu.ReleaseMutex();
return c;
}
}
public override Composite MoveToTargetBehavior
{
get
{
_mu.WaitOne();
Composite c = _cc.MoveToTargetBehavior;
_mu.ReleaseMutex();
return c;
}
}
public override bool NeedCombatBuffs
{
get
{
_mu.WaitOne();
bool b = _cc.NeedCombatBuffs;
_mu.ReleaseMutex();
return b;
}
}
public override bool NeedHeal
{
get
{
_mu.WaitOne();
bool b = _cc.NeedHeal;
_mu.ReleaseMutex();
return b;
}
}
public override bool NeedPreCombatBuffs
{
get
{
_mu.WaitOne();
bool b = _cc.NeedPreCombatBuffs;
_mu.ReleaseMutex();
return b;
}
}
public override bool NeedPullBuffs
{
get
{
_mu.WaitOne();
bool b = _cc.NeedPullBuffs;
_mu.ReleaseMutex();
return b;
}
}
public override bool NeedRest
{
get
{
_mu.WaitOne();
bool b = _cc.NeedRest;
_mu.ReleaseMutex();
return b;
}
}
public override Composite PreCombatBuffBehavior
{
get
{
_mu.WaitOne();
Composite c = _cc.PreCombatBuffBehavior;
_mu.ReleaseMutex();
return c;
}
}
public override Composite PullBehavior
{
get
{
_mu.WaitOne();
Composite c = _cc.PullBehavior;
_mu.ReleaseMutex();
return c;
}
}
public override Composite PullBuffBehavior
{
get
{
_mu.WaitOne();
Composite c = _cc.PullBehavior;
_mu.ReleaseMutex();
return c;
}
}
public override double? PullDistance
{
get
{
_mu.WaitOne();
double? d = _cc.PullDistance;
_mu.ReleaseMutex();
return d;
}
}
public override Composite RestBehavior
{
get
{
_mu.WaitOne();
Composite c = _cc.RestBehavior;
_mu.ReleaseMutex();
return c;
}
}
public override bool WantButton
{
get
{
_mu.WaitOne();
bool b = _cc.WantButton;
_mu.ReleaseMutex();
return b;
}
}
public override void Combat()
{
_mu.WaitOne();
_cc.Combat();
_mu.ReleaseMutex();
}
public override void CombatBuff()
{
_mu.WaitOne();
_cc.CombatBuff();
_mu.ReleaseMutex();
}
public override void Heal()
{
_mu.WaitOne();
_cc.Heal();
_mu.ReleaseMutex();
}
public override void Initialize()
{
_mu.WaitOne();
_cc.Initialize();
_mu.ReleaseMutex();
}
public override void OnButtonPress()
{
_mu.WaitOne();
_cc.OnButtonPress();
_mu.ReleaseMutex();
}
public override void PreCombatBuff()
{
_mu.WaitOne();
_cc.PreCombatBuff();
_mu.ReleaseMutex();
}
public override void Pull()
{
_mu.WaitOne();
_cc.Pull();
_mu.ReleaseMutex();
}
public override void PullBuff()
{
_mu.WaitOne();
_cc.PullBuff();
_mu.ReleaseMutex();
}
public override void Pulse()
{
_mu.WaitOne();
_cc.Pulse();
_mu.ReleaseMutex();
}
public override void Rest()
{
_mu.WaitOne();
_cc.Rest();
_mu.ReleaseMutex();
}
public override void ShutDown()
{
_mu.WaitOne();
_cc.ShutDown();
_mu.ReleaseMutex();
}
private string[] ASSEMBLIES
{
get
{
return new string[]
{
"Honorbuddy.exe",
"fasmdll_managed.dll",
"Tripper.XNAMath.dll",
"System.dll",
"System.Core.dll",
"System.Xml.Linq.dll"
};
}
}
private string[] INCLUDED_EXTENSIONS
{
get
{
return new string[]
{
".cs",
".resx",
".res"
};
}
}
}
}
tldr: CC reloads itself when it detects changes to itself lolz
Last edited: