//+------------------------------------------------------------------+ //| 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 //+------------------------------------------------------------------+