2026-04-18 11:31:50 +00:00

475 lines
18 KiB
C#

// 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<CableAlimData> _cablesAlim = new SyncList<CableAlimData>();
private readonly SyncList<CableRJ45Data> _cablesRJ45 = new SyncList<CableRJ45Data>();
// ============================================================
// 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<string> _cablesAlimRecrees = new HashSet<string>();
private readonly HashSet<string> _cablesRJ45Recrees = new HashSet<string>();
// Cables en attente : ceux qu'on a tente de recreer mais dont les refs etaient null
private readonly List<CableAlimData> _attenteAlim = new List<CableAlimData>();
private readonly List<CableRJ45Data> _attenteRJ45 = new List<CableRJ45Data>();
// ============================================================
// 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
// ============================================================
/// <summary>
/// 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.
/// </summary>
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<CableAlimentation>();
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<PointAccroche> chemin = ConstruireCheminAuto(
portSource.GetPointConnexion(), portDest.GetPointConnexion());
GameObject cableObj = new GameObject($"CableRJ45_rejoin_{data.equipSourceNetId}_{data.portSourceIndex}");
CableRJ45 cable = cableObj.AddComponent<CableRJ45>();
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<PortRJ45>();
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<PDU>();
if (pdu != null && indexPrise >= 0 && indexPrise < pdu.prises.Count)
return pdu.prises[indexPrise];
PriseC13[] prises = pduGO.GetComponentsInChildren<PriseC13>();
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<PortAlimentation>();
if (indexPort >= 0 && indexPort < ports.Length) return ports[indexPort];
return null;
}
private List<PointAccroche> ConstruireCheminAuto(Vector3 posSource, Vector3 posDest)
{
// Algorithme A* simplifie (copie depuis CablageReseau)
List<PointAccroche> chemin = new List<PointAccroche>();
PointAccroche[] tousPoints = FindObjectsOfType<PointAccroche>();
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<PointAccroche, float> gScore = new Dictionary<PointAccroche, float>();
Dictionary<PointAccroche, PointAccroche> cameFrom = new Dictionary<PointAccroche, PointAccroche>();
HashSet<PointAccroche> closedSet = new HashSet<PointAccroche>();
List<PointAccroche> openSet = new List<PointAccroche>();
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<PointAccroche> result = new List<PointAccroche> { 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;
}
}