475 lines
18 KiB
C#
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;
|
|
}
|
|
}
|