Warrior_EA/Expert/ConfigLock/ConfigLock.mqh

118 lines
5.8 KiB
MQL5
Raw Permalink Normal View History

//+------------------------------------------------------------------+
//| Warrior_EA |
//| AnimateDread |
//| |
//| THE PER-CONFIG CHART LOCK. STATELESS-except-one-field, same |
//| doctrine as BarrierHorizon\BarrierHorizon.mqh: m_configLockName |
//| is genuinely exclusive (grep-verified against the rest of |
//| Expert\ - nothing outside Lifecycle.mqh ever touched it), so this |
//| collaborator owns it as a real member instead of reaching it |
//| through the view. Every method below is a pure relocation of |
//| Expert\AIBase\Lifecycle.mqh's original AcquireConfigLock/ |
//| ReleaseConfigLock bodies - same order, same conditionals, no |
//| logic changes (the repeated m_activeFileName reads are cached to |
//| one local, since the view call replaces a direct field read that |
//| was itself invariant for the duration of the call). AcquireConfigLock |
//| has exactly one external caller (Topology.mqh, grep-verified), |
//| which keeps calling it unqualified - nothing to rewire. |
//+------------------------------------------------------------------+
#ifndef WARRIOR_CONFIGLOCK_CONFIGLOCK_MQH
#define WARRIOR_CONFIGLOCK_CONFIGLOCK_MQH
//--- Set when a start is refused for a reason no retry can change. OnInit's retry loop (Warrior_EA.mq5)
//--- reads it so the operator's LAST log line names the cause instead of "Failed to initialize Indicators".
string g_initFatalReason = "";
class CConfigLock
{
private:
CConfigLockView *m_view; // BORROWED - the signal owns the adapter, not the reverse
string m_configLockName;
public:
CConfigLock(void) : m_view(NULL), m_configLockName("") { }
void Bind(CConfigLockView *view) { m_view = view; }
bool Acquire(void);
void Release(void);
};
//+------------------------------------------------------------------+
//| Claim the resolved model filename for this chart, terminal-wide. |
//+------------------------------------------------------------------+
bool CConfigLock::Acquire(void)
{
string activeFileName = m_view.ActiveFileName();
//--- FNV-1a over the resolved filename: every retrain-affecting input is already folded into that
//--- name, so equal names mean genuinely equal configs and nothing else has to be compared. Hashed
//--- because MQL5 caps global-variable names at 63 characters and the path alone exceeds that.
uint h = 2166136261;
int len = StringLen(activeFileName);
for(int i = 0; i < len; i++)
{
h ^= (uint)StringGetCharacter(activeFileName, i);
h *= 16777619;
}
string name = "WarriorAI_" + m_view.ShortId() + "_" + StringFormat("%08x", h);
long self = ChartID();
//--- Atomic: true means it did not exist and is now ours.
if(GlobalVariableTemp(name))
{
GlobalVariableSet(name, (double)self);
m_configLockName = name;
return true;
}
long owner = (long)GlobalVariableGet(name);
//--- Our own entry: this chart is re-initializing after a parameter change or a recompile whose
//--- OnDeinit never reached Release(). Reclaim it instead of refusing to start.
if(owner == self)
{
m_configLockName = name;
return true;
}
//--- Owner recorded but its chart no longer runs an expert - take the claim over. owner == 0 is
//--- deliberately NOT treated as stale: it means another instance created the variable microseconds
//--- ago and has not stamped its id yet, which is a live claim, not a dead one.
bool ownerAlive = false;
if(owner != 0)
{
long id = ChartFirst();
while(id >= 0)
{
if(id == owner)
{
ownerAlive = (StringLen(ChartGetString(id, CHART_EXPERT_NAME)) > 0);
break;
}
id = ChartNext(id);
}
}
if(owner != 0 && !ownerAlive)
{
GlobalVariableSet(name, (double)self);
m_configLockName = name;
return true;
}
Print(m_view.Id() + ": REFUSED to start - another chart is already training this exact configuration. Both" +
" would save into the same files (" + activeFileName + ".nnw plus its .cfg/.stats/checkpoints)" +
" and overwrite each other's progress with no error reported anywhere. Owner: " +
(owner != 0 ? "chart " + IntegerToString(owner) + " (" + ChartSymbol(owner) + " " +
EnumToString((ENUM_TIMEFRAMES)ChartPeriod(owner)) + ")" : "another chart, still initializing") +
". Change the enabled NN set (Use_MLP/Use_CONV/Use_LSTM/Use_CONVLSTM) or any retrain-affecting" +
" input on THIS chart so it trains its own model, or remove one of the two charts. Note the" +
" private build defaults ALL FOUR direction NNs on - two same-symbol charts left at their" +
" defaults land here.");
g_initFatalReason = "another chart already owns this configuration (" + activeFileName + ")";
return false;
}
//+------------------------------------------------------------------+
//| Drop this instance's claim (see Acquire). |
//+------------------------------------------------------------------+
void CConfigLock::Release(void)
{
if(StringLen(m_configLockName) == 0)
return;
//--- Only delete a claim we still hold: if a later instance took this entry over via the stale-owner
//--- path above, deleting it here would silently hand the config to a third chart.
if((long)GlobalVariableGet(m_configLockName) == ChartID())
GlobalVariableDel(m_configLockName);
m_configLockName = "";
}
#endif // WARRIOR_CONFIGLOCK_CONFIGLOCK_MQH
//+------------------------------------------------------------------+