using System.Collections.Generic;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
using UnityEngine;
namespace AutomaticDoorSystem.Utilities
{
///
/// Reads door entities into plain data the pool managers can use. Runs before both pools so
/// their Awake finds an Instance already, instead of racing to add a second bridge.
///
[DefaultExecutionOrder(-100)]
public class DoorDataBridge : MonoBehaviour
{
public static DoorDataBridge Instance { get; private set; }
public struct DoorInfo
{
public int doorId;
public Vector3 position;
public Quaternion rotation;
public DoorType doorType;
public OpeningStyle openingStyle;
public Vector3 slideOffset;
public Vector3 triggerSize;
public Vector3 triggerCenter;
/// Where the pooled AudioSource is placed: the baked audioAnchor override,
/// or the world centre of the trigger volume when no anchor was assigned.
public Vector3 audioPosition;
public bool isLocked;
public Entity rootEntity;
/// Baked from DoorAuthoring.doorAudioConfig; null for a silent door.
public DoorAudioConfiguration audioConfig;
}
public struct DoorPanelInfo
{
public Entity panelEntity;
public Vector3 position;
public Quaternion rotation;
public bool isLeftPanel;
public Vector3 colliderSize;
public Vector3 colliderCenter;
public bool hasColliderData;
/// Baked from the panel's SteamAudioDynamicObject; null for a panel without
/// Steam Audio geometry. The collider pool attaches it to the pooled panel proxy.
public SteamAudio.SerializedData audioGeometry;
}
private EntityManager _entityManager;
private EntityQuery _doorQuery;
private List _cachedDoorInfo;
private Dictionary _doorEntityCache;
private float _cacheRefreshTimer;
private bool _duplicateIdWarningLogged;
private const float CACHE_REFRESH_INTERVAL = 1f;
private DoorPanelInfo[] _panelArrayCache = new DoorPanelInfo[4];
public int GetDoorPanelCount(int doorId)
{
if (_entityManager == null || _doorQuery == null)
return 0;
var doorComponents = _doorQuery.ToComponentDataArray(Allocator.Temp);
var entities = _doorQuery.ToEntityArray(Allocator.Temp);
int panelCount = 0;
for (int i = 0; i < doorComponents.Length; i++)
{
if (doorComponents[i].DoorId == doorId)
{
if (_entityManager.HasBuffer(entities[i]))
{
var buffer = _entityManager.GetBuffer(entities[i]);
panelCount = buffer.Length;
}
break;
}
}
doorComponents.Dispose();
entities.Dispose();
return panelCount;
}
public bool TryGetDoorPanels(int doorId, out DoorPanelInfo[] panels, out int panelCount)
{
panels = null;
panelCount = 0;
if (_entityManager == null || !_doorEntityCache.TryGetValue(doorId, out Entity doorEntity))
return false;
if (!_entityManager.HasBuffer(doorEntity))
return false;
var buffer = _entityManager.GetBuffer(doorEntity);
panelCount = buffer.Length;
if (_panelArrayCache.Length < panelCount)
{
_panelArrayCache = new DoorPanelInfo[panelCount];
}
for (int j = 0; j < panelCount; j++)
{
var bufferElement = buffer[j];
var panelEntity = bufferElement.DoorEntity;
if (_entityManager.HasComponent(panelEntity))
{
var transform = _entityManager.GetComponentData(panelEntity);
bool isLeftPanel = bufferElement.IsLeftDoor == 1;
_panelArrayCache[j] = new DoorPanelInfo
{
panelEntity = panelEntity,
position = transform.Position,
rotation = transform.Rotation,
isLeftPanel = isLeftPanel,
colliderSize = bufferElement.ColliderSize,
colliderCenter = bufferElement.ColliderCenter,
hasColliderData = bufferElement.HasColliderData == 1,
audioGeometry = bufferElement.AudioGeometry.Value
};
}
}
panels = _panelArrayCache;
return true;
}
private void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(this);
return;
}
Instance = this;
_cachedDoorInfo = new List(500);
_doorEntityCache = new Dictionary();
_cacheRefreshTimer = 0f;
}
private void OnDestroy()
{
if (Instance == this)
{
Instance = null;
}
}
private void OnEnable()
{
var world = World.DefaultGameObjectInjectionWorld;
if (world != null)
{
_entityManager = world.EntityManager;
_doorQuery = _entityManager.CreateEntityQuery(
typeof(DoorComponent),
typeof(DoorStateComponent),
typeof(DoorTransformData),
typeof(LocalToWorld),
typeof(DoorTriggerVolume)
);
}
_cacheRefreshTimer = 0f;
}
private void Start()
{
Invoke(nameof(InitialRefresh), 0.2f);
}
private void InitialRefresh()
{
RefreshAllDoorInfoCache();
if (_cachedDoorInfo.Count == 0)
{
Invoke(nameof(InitialRefresh), 0.2f);
}
}
private void Update()
{
_cacheRefreshTimer += Time.deltaTime;
if (!(_cacheRefreshTimer >= CACHE_REFRESH_INTERVAL)) return;
RefreshAllDoorInfoCache();
_cacheRefreshTimer = 0f;
}
private void RefreshAllDoorInfoCache()
{
if (_entityManager == null || _doorQuery == null)
{
Debug.LogWarning("[DoorDataBridge] EntityManager or Query not initialized");
return;
}
_cachedDoorInfo.Clear();
_doorEntityCache.Clear();
var doorComponents = _doorQuery.ToComponentDataArray(Allocator.Temp);
var doorStates = _doorQuery.ToComponentDataArray(Allocator.Temp);
var doorTransforms = _doorQuery.ToComponentDataArray(Allocator.Temp);
var transforms = _doorQuery.ToComponentDataArray(Allocator.Temp);
var triggers = _doorQuery.ToComponentDataArray(Allocator.Temp);
var entities = _doorQuery.ToEntityArray(Allocator.Temp);
int duplicateIds = 0;
for (int i = 0; i < doorComponents.Length; i++)
{
int doorId = doorComponents[i].DoorId;
if (_doorEntityCache.ContainsKey(doorId))
{
duplicateIds++;
}
_doorEntityCache[doorId] = entities[i];
GetAudioData(entities[i], triggers[i].Center, out var audioConfig, out var audioAnchorLocal);
var doorInfo = new DoorInfo
{
doorId = doorId,
position = transforms[i].Position,
rotation = transforms[i].Rotation,
doorType = doorComponents[i].Type,
openingStyle = doorTransforms[i].OpeningStyle,
slideOffset = doorTransforms[i].SlideOffset,
triggerSize = triggers[i].Size,
triggerCenter = triggers[i].Center,
audioPosition = math.transform(transforms[i].Value, audioAnchorLocal),
isLocked = doorStates[i].IsLocked == 1,
rootEntity = entities[i],
audioConfig = audioConfig
};
_cachedDoorInfo.Add(doorInfo);
}
if (duplicateIds > 0 && !_duplicateIdWarningLogged)
{
_duplicateIdWarningLogged = true;
Debug.LogError(
$"[DoorDataBridge] {duplicateIds} door(s) reuse a Door Id already taken by another door " +
$"({doorComponents.Length} doors, {_doorEntityCache.Count} distinct ids). Door Ids key every " +
"lookup in this system, so the duplicates will get no colliders and no audio. " +
"Run Tools > AutomaticDoorSystem > Setup Validator to find and renumber them.", this);
}
entities.Dispose();
doorTransforms.Dispose();
doorComponents.Dispose();
doorStates.Dispose();
transforms.Dispose();
triggers.Dispose();
}
public List GetAllDoorInfo()
{
return _cachedDoorInfo;
}
public bool TryGetDoorInfo(int doorId, out DoorInfo info)
{
info = default;
if (_entityManager == null)
{
return false;
}
// Subscenes stream in and out (elevators load per floor), so a cached Entity handle
// can be dead by the time it is used - and the timed refresh does not run while the
// editor is paused. A stale hit triggers one immediate refresh so the lookup answers
// from live data instead of failing until the next timer tick.
if (!TryGetLiveDoorEntity(doorId, out Entity doorEntity))
{
RefreshAllDoorInfoCache();
if (!TryGetLiveDoorEntity(doorId, out doorEntity))
{
return false;
}
}
var doorComponent = _entityManager.GetComponentData(doorEntity);
var doorState = _entityManager.GetComponentData(doorEntity);
var doorTransform = _entityManager.GetComponentData(doorEntity);
var transform = _entityManager.GetComponentData(doorEntity);
var trigger = _entityManager.GetComponentData(doorEntity);
GetAudioData(doorEntity, trigger.Center, out var audioConfig, out var audioAnchorLocal);
info = new DoorInfo
{
doorId = doorComponent.DoorId,
position = transform.Position,
rotation = transform.Rotation,
doorType = doorComponent.Type,
openingStyle = doorTransform.OpeningStyle,
slideOffset = doorTransform.SlideOffset,
triggerSize = trigger.Size,
triggerCenter = trigger.Center,
audioPosition = math.transform(transform.Value, audioAnchorLocal),
isLocked = doorState.IsLocked == 1,
rootEntity = doorEntity,
audioConfig = audioConfig
};
return true;
}
/// A cached entity for the id that is still alive and still a door.
private bool TryGetLiveDoorEntity(int doorId, out Entity doorEntity)
{
return _doorEntityCache.TryGetValue(doorId, out doorEntity)
&& _entityManager.Exists(doorEntity)
&& _entityManager.HasComponent(doorEntity);
}
///
/// Resolves the DoorAudioConfiguration and audio anchor baked onto the door entity.
/// Doors baked before the config moved onto DoorAuthoring have no such component, hence
/// the HasComponent guard; those fall back to the trigger volume centre for the anchor.
///
private void GetAudioData(Entity doorEntity, float3 triggerCenter, out DoorAudioConfiguration config, out float3 anchorLocalPosition)
{
config = null;
anchorLocalPosition = triggerCenter;
if (!_entityManager.HasComponent(doorEntity))
return;
var reference = _entityManager.GetComponentData(doorEntity);
config = reference.Config.Value;
anchorLocalPosition = reference.AnchorLocalPosition;
}
public bool IsDoorLocked(int doorId)
{
if (_entityManager == null || !_doorEntityCache.TryGetValue(doorId, out Entity doorEntity))
{
return false;
}
if (!_entityManager.HasComponent(doorEntity))
{
return false;
}
var doorState = _entityManager.GetComponentData(doorEntity);
return doorState.IsLocked == 1;
}
}
}