// GestionnaireCablesReseau.cs - v1.0 // // Singleton NetworkBehaviour qui synchronise les cables (alimentation + RJ45) entre // le serveur et les clients, y compris au moment du rejoin d'un client. // // Architecture : // - Cote serveur : chaque creation de cable est enregistree dans une SyncList. // La suppression retire l'entree correspondante. // - Cote client au rejoin (OnStartClient) : parcours des SyncLists et recreation // locale de chaque cable. On attend d'abord que toutes les baies et PDUs soient // "pretes" (via un systeme d'event) pour eviter les refs null sur les ports. // // Setup Unity : // 1. Dans la scene Datacenter_01, creer un GameObject "GestionnaireCables_Holder" // 2. Add Component : NetworkIdentity (rien coche) // 3. Add Component : GestionnaireCablesReseau // 4. Sauver la scene // // Le gestionnaire est appele depuis CablageReseau.CmdCreerCable* / CmdSupprimerCable* // via GestionnaireCablesReseau.Instance. using UnityEngine; using Mirror; using System.Collections; using System.Collections.Generic; public class GestionnaireCablesReseau : NetworkBehaviour { public static GestionnaireCablesReseau Instance { get; private set; } // ============================================================ // STRUCTURES DE DONNEES // ============================================================ [System.Serializable] public struct CableAlimData { public uint pduNetId; public int indexPrise; public uint equipNetId; public int indexPortAlim; public Color couleur; public string Id => $"A_{pduNetId}_{indexPrise}_{equipNetId}_{indexPortAlim}"; } [System.Serializable] public struct CableRJ45Data { public uint equipSourceNetId; public int portSourceIndex; public uint equipDestNetId; public int portDestIndex; public Color couleur; public float longueur; public string Id => $"R_{equipSourceNetId}_{portSourceIndex}_{equipDestNetId}_{portDestIndex}"; } // SyncLists — synchronisees automatiquement aux clients private readonly SyncList _cablesAlim = new SyncList(); private readonly SyncList _cablesRJ45 = new SyncList(); // ============================================================ // EVENT : "Baies et equipements pretes pour recevoir les cables" // ============================================================ // // Probleme : au rejoin, les baies sont spawnees dans un ordre imprevisible. // Si on tente de recreer un cable alors que la baie+PDU cibles ne sont pas // encore pretes (BaieNetworkSetup.OnStartClient pas encore termine), on aura // des refs null. // // Solution : chaque BaieNetworkSetup notifie le gestionnaire quand sa baie // est prete. Quand on recoit une notification, on re-tente les cables en // attente (ceux qui referencaient cette baie/PDU). // Structures de "cables en attente" cote client private readonly HashSet _cablesAlimRecrees = new HashSet(); private readonly HashSet _cablesRJ45Recrees = new HashSet(); // Cables en attente : ceux qu'on a tente de recreer mais dont les refs etaient null private readonly List _attenteAlim = new List(); private readonly List _attenteRJ45 = new List(); // ============================================================ // LIFECYCLE // ============================================================ void Awake() { if (Instance != null && Instance != this) { Destroy(gameObject); return; } Instance = this; } void OnDestroy() { if (Instance == this) Instance = null; } public override void OnStartClient() { base.OnStartClient(); // Sur le host, rien a faire : le serveur a deja cree les cables en live if (NetworkServer.active) return; Debug.Log($"[GestionnaireCablesReseau] Client rejoint : {_cablesAlim.Count} cables alim, {_cablesRJ45.Count} cables RJ45 a recreer"); // Lancer la coroutine qui tentera de recreer les cables regulierement // jusqu'a ce que tous soient crees (ou abandon apres timeout) StartCoroutine(RejouerCablesProgressivement()); } // ============================================================ // API SERVEUR (appelee par CablageReseau) // ============================================================ [Server] public void EnregistrerCableAlim(uint pduNetId, int indexPrise, uint equipNetId, int indexPortAlim, Color couleur) { CableAlimData data = new CableAlimData { pduNetId = pduNetId, indexPrise = indexPrise, equipNetId = equipNetId, indexPortAlim = indexPortAlim, couleur = couleur }; // Eviter les doublons (meme prise + meme port = meme cable) for (int i = 0; i < _cablesAlim.Count; i++) { if (_cablesAlim[i].pduNetId == pduNetId && _cablesAlim[i].indexPrise == indexPrise && _cablesAlim[i].equipNetId == equipNetId && _cablesAlim[i].indexPortAlim == indexPortAlim) return; } _cablesAlim.Add(data); Debug.Log($"[GestionnaireCablesReseau] Cable alim enregistre (total : {_cablesAlim.Count})"); } [Server] public void OublierCableAlim(uint pduNetId, int indexPrise, uint equipNetId, int indexPortAlim) { for (int i = _cablesAlim.Count - 1; i >= 0; i--) { if (_cablesAlim[i].pduNetId == pduNetId && _cablesAlim[i].indexPrise == indexPrise && _cablesAlim[i].equipNetId == equipNetId && _cablesAlim[i].indexPortAlim == indexPortAlim) { _cablesAlim.RemoveAt(i); Debug.Log($"[GestionnaireCablesReseau] Cable alim supprime (reste : {_cablesAlim.Count})"); return; } } } [Server] public void EnregistrerCableRJ45(uint equipSourceNetId, int portSourceIndex, uint equipDestNetId, int portDestIndex, Color couleur, float longueur) { CableRJ45Data data = new CableRJ45Data { equipSourceNetId = equipSourceNetId, portSourceIndex = portSourceIndex, equipDestNetId = equipDestNetId, portDestIndex = portDestIndex, couleur = couleur, longueur = longueur }; for (int i = 0; i < _cablesRJ45.Count; i++) { if (_cablesRJ45[i].equipSourceNetId == equipSourceNetId && _cablesRJ45[i].portSourceIndex == portSourceIndex && _cablesRJ45[i].equipDestNetId == equipDestNetId && _cablesRJ45[i].portDestIndex == portDestIndex) return; } _cablesRJ45.Add(data); Debug.Log($"[GestionnaireCablesReseau] Cable RJ45 enregistre (total : {_cablesRJ45.Count})"); } [Server] public void OublierCableRJ45(uint equipSourceNetId, int portSourceIndex, uint equipDestNetId, int portDestIndex) { for (int i = _cablesRJ45.Count - 1; i >= 0; i--) { var c = _cablesRJ45[i]; // Match dans les deux sens car un cable RJ45 est bidirectionnel bool match1 = c.equipSourceNetId == equipSourceNetId && c.portSourceIndex == portSourceIndex && c.equipDestNetId == equipDestNetId && c.portDestIndex == portDestIndex; bool match2 = c.equipSourceNetId == equipDestNetId && c.portSourceIndex == portDestIndex && c.equipDestNetId == equipSourceNetId && c.portDestIndex == portSourceIndex; if (match1 || match2) { _cablesRJ45.RemoveAt(i); Debug.Log($"[GestionnaireCablesReseau] Cable RJ45 supprime (reste : {_cablesRJ45.Count})"); return; } } } // ============================================================ // API CLIENT : notification de baie prete // ============================================================ /// /// Appele par BaieNetworkSetup.OnStartClient une fois que la baie + ses PDUs /// sont genere localement. Le gestionnaire tente de recreer les cables en /// attente qui referencaient cette baie ou ses enfants. /// public void NotifierBaiePrete(GameObject baie) { if (!NetworkClient.active || NetworkServer.active) return; Debug.Log($"[GestionnaireCablesReseau] Baie prete : {baie.name}, retry cables en attente"); RetenterCablesEnAttente(); } // ============================================================ // COROUTINE DE REJEU (client qui rejoint) // ============================================================ private IEnumerator RejouerCablesProgressivement() { // Attendre 0.5s pour laisser les premieres baies + PDUs se generer via BaieNetworkSetup yield return new WaitForSeconds(0.5f); // Initialiser la liste d'attente avec tous les cables connus _attenteAlim.Clear(); foreach (var data in _cablesAlim) _attenteAlim.Add(data); _attenteRJ45.Clear(); foreach (var data in _cablesRJ45) _attenteRJ45.Add(data); // Tenter une premiere passe RetenterCablesEnAttente(); // Si il reste des cables en attente, re-tenter periodiquement // (les notifications de baie prete vont aussi declencher des retries) float tempsEcoule = 0f; const float timeoutMax = 15f; const float intervalRetry = 0.5f; while ((_attenteAlim.Count > 0 || _attenteRJ45.Count > 0) && tempsEcoule < timeoutMax) { yield return new WaitForSeconds(intervalRetry); tempsEcoule += intervalRetry; RetenterCablesEnAttente(); } if (_attenteAlim.Count > 0 || _attenteRJ45.Count > 0) { Debug.LogWarning($"[GestionnaireCablesReseau] Timeout : {_attenteAlim.Count} cables alim + {_attenteRJ45.Count} cables RJ45 jamais recrees"); } else { Debug.Log($"[GestionnaireCablesReseau] Tous les cables ont ete recrees"); } } private void RetenterCablesEnAttente() { // Cables alim for (int i = _attenteAlim.Count - 1; i >= 0; i--) { CableAlimData data = _attenteAlim[i]; if (_cablesAlimRecrees.Contains(data.Id)) { _attenteAlim.RemoveAt(i); continue; } if (TenterRecreerCableAlim(data)) { _cablesAlimRecrees.Add(data.Id); _attenteAlim.RemoveAt(i); } } // Cables RJ45 for (int i = _attenteRJ45.Count - 1; i >= 0; i--) { CableRJ45Data data = _attenteRJ45[i]; if (_cablesRJ45Recrees.Contains(data.Id)) { _attenteRJ45.RemoveAt(i); continue; } if (TenterRecreerCableRJ45(data)) { _cablesRJ45Recrees.Add(data.Id); _attenteRJ45.RemoveAt(i); } } } // ============================================================ // RECREATION LOCALE DES CABLES // ============================================================ private bool TenterRecreerCableAlim(CableAlimData data) { PriseC13 prise = TrouverPriseC13(data.pduNetId, data.indexPrise); PortAlimentation port = TrouverPortAlimentation(data.equipNetId, data.indexPortAlim); if (prise == null || port == null) return false; // Deja connecte ? (si BaieNetworkSetup l'a fait automatiquement via InstallerPDUAutomatiquement) if (prise.estConnectee || port.estConnecte) return true; GameObject cableObj = new GameObject($"CableAlim_rejoin_{data.pduNetId}_{data.indexPrise}"); CableAlimentation cable = cableObj.AddComponent(); cable.couleurCable = data.couleur; cable.Initialiser(prise, port); prise.Connecter(cable, port); port.Connecter(cable, prise); Debug.Log($"[GestionnaireCablesReseau] Cable alim recree : PDU {data.pduNetId} prise #{data.indexPrise} -> {port.nomPort}"); return true; } private bool TenterRecreerCableRJ45(CableRJ45Data data) { PortRJ45 portSource = TrouverPortRJ45(data.equipSourceNetId, data.portSourceIndex); PortRJ45 portDest = TrouverPortRJ45(data.equipDestNetId, data.portDestIndex); if (portSource == null || portDest == null) return false; // Deja connecte ? if (portSource.cable != null || portDest.cable != null) return true; List chemin = ConstruireCheminAuto( portSource.GetPointConnexion(), portDest.GetPointConnexion()); GameObject cableObj = new GameObject($"CableRJ45_rejoin_{data.equipSourceNetId}_{data.portSourceIndex}"); CableRJ45 cable = cableObj.AddComponent(); cable.Initialiser(portSource, portDest, chemin, data.couleur, data.longueur, null); portSource.Connecter(portDest, cable); portDest.Connecter(portSource, cable); Debug.Log($"[GestionnaireCablesReseau] Cable RJ45 recree : {portSource.nomPort} -> {portDest.nomPort}"); return true; } // ============================================================ // HELPERS DE RECHERCHE (copies depuis CablageReseau) // ============================================================ private PortRJ45 TrouverPortRJ45(uint equipNetId, int portIndex) { if (!NetworkClient.spawned.ContainsKey(equipNetId)) return null; GameObject equipGO = NetworkClient.spawned[equipNetId].gameObject; if (equipGO == null) return null; PortRJ45[] ports = equipGO.GetComponentsInChildren(); foreach (var port in ports) if (port.numeroPort == portIndex) return port; if (portIndex >= 0 && portIndex < ports.Length) return ports[portIndex]; return null; } private PriseC13 TrouverPriseC13(uint pduNetId, int indexPrise) { if (!NetworkClient.spawned.ContainsKey(pduNetId)) return null; GameObject pduGO = NetworkClient.spawned[pduNetId].gameObject; if (pduGO == null) return null; PDU pdu = pduGO.GetComponent(); if (pdu != null && indexPrise >= 0 && indexPrise < pdu.prises.Count) return pdu.prises[indexPrise]; PriseC13[] prises = pduGO.GetComponentsInChildren(); foreach (var p in prises) if (p.indexPrise == indexPrise) return p; return null; } private PortAlimentation TrouverPortAlimentation(uint equipNetId, int indexPort) { if (!NetworkClient.spawned.ContainsKey(equipNetId)) return null; GameObject equipGO = NetworkClient.spawned[equipNetId].gameObject; if (equipGO == null) return null; PortAlimentation[] ports = equipGO.GetComponentsInChildren(); if (indexPort >= 0 && indexPort < ports.Length) return ports[indexPort]; return null; } private List ConstruireCheminAuto(Vector3 posSource, Vector3 posDest) { // Algorithme A* simplifie (copie depuis CablageReseau) List chemin = new List(); PointAccroche[] tousPoints = FindObjectsOfType(); if (tousPoints.Length == 0) return chemin; PointAccroche ptDepart = null; float bestDistDepart = 3f; foreach (var p in tousPoints) { float d = Vector3.Distance(posSource, p.GetPosition()); if (d < bestDistDepart) { bestDistDepart = d; ptDepart = p; } } PointAccroche ptArrivee = null; float bestDistArrivee = 3f; foreach (var p in tousPoints) { float d = Vector3.Distance(posDest, p.GetPosition()); if (d < bestDistArrivee) { bestDistArrivee = d; ptArrivee = p; } } if (ptDepart == null || ptArrivee == null) return chemin; if (ptDepart == ptArrivee) { chemin.Add(ptDepart); return chemin; } float distanceVoisinage = 1.5f; Dictionary gScore = new Dictionary(); Dictionary cameFrom = new Dictionary(); HashSet closedSet = new HashSet(); List openSet = new List(); gScore[ptDepart] = 0; openSet.Add(ptDepart); int maxIter = 500; int iter = 0; while (openSet.Count > 0 && iter < maxIter) { iter++; PointAccroche current = null; float bestF = float.MaxValue; foreach (var n in openSet) { float g = gScore.ContainsKey(n) ? gScore[n] : float.MaxValue; float f = g + Vector3.Distance(n.GetPosition(), ptArrivee.GetPosition()); if (f < bestF) { bestF = f; current = n; } } if (current == null) break; if (current == ptArrivee) { List result = new List { current }; while (cameFrom.ContainsKey(current)) { current = cameFrom[current]; result.Insert(0, current); } return result; } openSet.Remove(current); closedSet.Add(current); foreach (var autre in tousPoints) { if (autre == current || closedSet.Contains(autre)) continue; if (Vector3.Distance(current.GetPosition(), autre.GetPosition()) > distanceVoisinage) continue; float tentG = gScore[current] + Vector3.Distance(current.GetPosition(), autre.GetPosition()); if (!openSet.Contains(autre)) openSet.Add(autre); else if (gScore.ContainsKey(autre) && tentG >= gScore[autre]) continue; cameFrom[autre] = current; gScore[autre] = tentG; } } chemin.Add(ptDepart); chemin.Add(ptArrivee); return chemin; } }