Query daemon presentation entities by viewport

This commit is contained in:
Meta Vens 2026-06-22 15:04:12 +01:00
commit 74b3b3d72a
3 changed files with 153 additions and 13 deletions

View file

@ -1910,6 +1910,10 @@ static void RequireDaemonRenderQueryAuthority(string root)
"public static int QueryCompassMarkers(",
"public static int[] QueryVisibleEntityIndices(",
"public static int QueryVisibleEntityIndices(",
"public static int[] QueryPresentationEntityIndices(",
"public static int QueryPresentationEntityIndices(",
"TryParseEntityIndex(run?.CurrentEntityKey)",
"ContainsPoint(viewport, entity.PositionX, entity.PositionZ)",
"AetheriaRuntimeDaemonEntityContact",
"public static bool TryQueryEntityContact(",
"public static AetheriaRuntimeDaemonEntityContact[] QueryEntityContacts(",
@ -2141,6 +2145,9 @@ static void RequireDaemonRenderQueryAuthority(string root)
"private readonly List<AetheriaRuntimeDaemonAsteroidBeltPose> _daemonAsteroidBeltPoses",
"private readonly List<AetheriaRuntimeDaemonCompassMarker> _daemonCompassMarkers",
"private readonly Dictionary<int, AetheriaRuntimeDaemonCompassMarker> _daemonCompassMarkersByEntityIndex",
"private IReadOnlyDictionary<int, Entity> _observedEntityFacadesByDaemonIndex",
"private readonly List<int> _daemonPresentationEntityIndices",
"private readonly HashSet<int> _daemonPresentationEntityIndicesSet",
"private readonly List<int> _daemonVisibleEntityIndices",
"private readonly HashSet<int> _daemonVisibleEntityIndicesSet",
"private readonly HashSet<int> _visibleDaemonEntityIndices",
@ -2155,6 +2162,7 @@ static void RequireDaemonRenderQueryAuthority(string root)
"AetheriaRuntimeRunCheckpointCommit daemonRun)",
"_daemonZoneSnapshot = daemonZone;",
"_daemonRunSnapshot = daemonRun;",
"_observedEntityFacadesByDaemonIndex = observedEntityFacadesByDaemonIndex;",
"AetheriaRuntimeDaemonRenderQueries.EvaluateGravityTerrainHeight(",
"AetheriaRuntimeDaemonRenderQueries.ResolveZoneRenderRadius(",
" 2000);",
@ -2174,8 +2182,11 @@ static void RequireDaemonRenderQueryAuthority(string root)
"beltPosesByBodyKey.TryGetValue(bodyKey, out var beltPose)",
"LoadAsteroidBelt(beltPose)",
"void LoadAsteroidBelt(AetheriaRuntimeDaemonAsteroidBeltPose beltPose)",
"foreach (var entitySnapshot in daemonZone?.Entities ?? Array.Empty<AetheriaRuntimeEntitySnapshotCommit>())",
"observedEntityFacadesByDaemonIndex.TryGetValue(entitySnapshot.EntityIndex, out var entity)",
"private void SyncDaemonEntityInstances()",
"AetheriaRuntimeDaemonRenderQueries.QueryPresentationEntityIndices(",
"_observedEntityFacadesByDaemonIndex.TryGetValue(entityIndex, out var entity)",
"Loading entity {entity.Name} from daemon presentation query",
"UnloadEntity(pair.Value.Entity)",
"private readonly Dictionary<int, EntityInstance> _entityInstancesByDaemonIndex",
"public IReadOnlyDictionary<int, EntityInstance> DaemonEntityInstances => _entityInstancesByDaemonIndex;",
"public bool TryGetEntityInstance(int daemonEntityIndex, out EntityInstance instance)",
@ -2229,6 +2240,13 @@ static void RequireDaemonRenderQueryAuthority(string root)
"ZoneRenderer still accepts the whole Unity GameSettings object instead of explicit renderer asset/tuning inputs.");
}
if (zoneRenderer.Contains("foreach (var entitySnapshot in daemonZone?.Entities", StringComparison.Ordinal) ||
zoneRenderer.Contains("Loading entity {entity.Name} from daemon entity snapshot", StringComparison.Ordinal))
{
throw new InvalidOperationException(
"ZoneRenderer must query daemon presentation entities instead of loading every daemon entity snapshot into a mirrored Unity level.");
}
var entityInstancePath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "EntityInstance.cs");
var entityInstance = File.Exists(entityInstancePath)
? File.ReadAllText(entityInstancePath)

View file

@ -98,6 +98,9 @@ public class ZoneRenderer : MonoBehaviour
new List<AetheriaRuntimeDaemonCompassMarker>();
private readonly Dictionary<int, AetheriaRuntimeDaemonCompassMarker> _daemonCompassMarkersByEntityIndex =
new Dictionary<int, AetheriaRuntimeDaemonCompassMarker>();
private IReadOnlyDictionary<int, Entity> _observedEntityFacadesByDaemonIndex;
private readonly List<int> _daemonPresentationEntityIndices = new List<int>();
private readonly HashSet<int> _daemonPresentationEntityIndicesSet = new HashSet<int>();
private readonly List<int> _daemonVisibleEntityIndices = new List<int>();
private readonly HashSet<int> _daemonVisibleEntityIndicesSet = new HashSet<int>();
private readonly HashSet<int> _visibleDaemonEntityIndices = new HashSet<int>();
@ -221,21 +224,13 @@ public class ZoneRenderer : MonoBehaviour
AetheriaRuntimeRunCheckpointCommit daemonRun = null)
{
ClearZone();
_observedEntityFacadesByDaemonIndex = observedEntityFacadesByDaemonIndex;
ApplyDaemonFrame(daemonZone, daemonRun);
RefreshDaemonBodyPoses();
RefreshDaemonAsteroidBeltPoses();
SyncDaemonBodyViews();
foreach (var entitySnapshot in daemonZone?.Entities ?? Array.Empty<AetheriaRuntimeEntitySnapshotCommit>())
{
if (entitySnapshot != null &&
observedEntityFacadesByDaemonIndex != null &&
observedEntityFacadesByDaemonIndex.TryGetValue(entitySnapshot.EntityIndex, out var entity))
{
Debug.Log($"Loading entity {entity.Name} from daemon entity snapshot {entitySnapshot.EntityIndex}");
LoadEntity(entity);
}
}
RefreshDaemonVisibleEntityInstances();
SyncDaemonEntityInstances();
foreach (var exit in _daemonWormholeExits)
AddWormhole(exit);
@ -529,6 +524,7 @@ public class ZoneRenderer : MonoBehaviour
RefreshDaemonAsteroidBeltPoses();
SyncDaemonBodyViews();
RefreshDaemonVisibleEntityInstances();
SyncDaemonEntityInstances();
RefreshDaemonCompassMarkers();
Plane[] planes = GeometryUtility.CalculateFrustumPlanes(MainCamera);
@ -792,6 +788,39 @@ public class ZoneRenderer : MonoBehaviour
_visibleDaemonEntityIndices.Add(entityIndex);
}
private void SyncDaemonEntityInstances()
{
AetheriaRuntimeDaemonRenderQueries.QueryPresentationEntityIndices(
_daemonRunSnapshot,
_daemonZoneSnapshot,
PerspectiveEntity?.DaemonEntityIndex ?? -1,
RenderSettings.TargetDetectionInfoThreshold,
ResolveDaemonRenderViewport(),
_daemonPresentationEntityIndices);
_daemonPresentationEntityIndicesSet.Clear();
foreach (var entityIndex in _daemonPresentationEntityIndices)
_daemonPresentationEntityIndicesSet.Add(entityIndex);
foreach (var entityIndex in _daemonPresentationEntityIndices)
{
if (_entityInstancesByDaemonIndex.ContainsKey(entityIndex) ||
_observedEntityFacadesByDaemonIndex == null ||
!_observedEntityFacadesByDaemonIndex.TryGetValue(entityIndex, out var entity))
{
continue;
}
Debug.Log($"Loading entity {entity.Name} from daemon presentation query {entityIndex}");
LoadEntity(entity);
}
foreach (var pair in _entityInstancesByDaemonIndex.ToArray())
{
if (!_daemonPresentationEntityIndicesSet.Contains(pair.Key))
UnloadEntity(pair.Value.Entity);
}
}
private float GetTerrainHeight(float2 position)
{
return (float)AetheriaRuntimeDaemonRenderQueries.EvaluateGravityTerrainHeight(

View file

@ -978,6 +978,71 @@ namespace GameCult.Aetheria.State.Verse
return entityIndices.Count;
}
public static int[] QueryPresentationEntityIndices(
AetheriaRuntimeRunCheckpointCommit? run,
AetheriaRuntimeZoneSnapshotCommit? zone,
int observerEntityIndex,
double minimumInfoGathered,
AetheriaRuntimeXzRect viewport)
{
var entityIndices = new List<int>();
QueryPresentationEntityIndices(run, zone, observerEntityIndex, minimumInfoGathered, viewport, entityIndices);
return entityIndices.Count == 0 ? Array.Empty<int>() : entityIndices.ToArray();
}
public static int QueryPresentationEntityIndices(
AetheriaRuntimeRunCheckpointCommit? run,
AetheriaRuntimeZoneSnapshotCommit? zone,
int observerEntityIndex,
double minimumInfoGathered,
AetheriaRuntimeXzRect viewport,
List<int> entityIndices)
{
if (entityIndices == null) throw new ArgumentNullException(nameof(entityIndices));
entityIndices.Clear();
if (zone == null)
return 0;
var selected = new HashSet<int>();
var currentEntityIndex = TryParseEntityIndex(run?.CurrentEntityKey);
if (currentEntityIndex >= 0)
selected.Add(currentEntityIndex);
var entities = BuildEntityMap(zone);
if (observerEntityIndex >= 0)
{
if (entities.TryGetValue(observerEntityIndex, out var observer))
{
selected.Add(observer.EntityIndex);
foreach (var contact in observer.Contacts ?? Array.Empty<AetheriaRuntimeEntityContactCommit>())
{
if (contact != null &&
contact.Visible &&
contact.InfoGathered > minimumInfoGathered &&
entities.ContainsKey(contact.TargetEntityIndex))
{
selected.Add(contact.TargetEntityIndex);
}
}
}
}
foreach (var entity in zone.Entities ?? Array.Empty<AetheriaRuntimeEntitySnapshotCommit>())
{
if (entity != null &&
entity.EntityIndex >= 0 &&
ContainsPoint(viewport, entity.PositionX, entity.PositionZ))
{
selected.Add(entity.EntityIndex);
}
}
foreach (var entityIndex in selected.OrderBy(index => index))
entityIndices.Add(entityIndex);
return entityIndices.Count;
}
public static bool TryQueryEntityContact(
AetheriaRuntimeZoneSnapshotCommit? zone,
int observerEntityIndex,
@ -1345,6 +1410,34 @@ namespace GameCult.Aetheria.State.Verse
return dx * dx + dz * dz <= radius * radius;
}
private static bool ContainsPoint(AetheriaRuntimeXzRect rect, double x, double z)
{
return x >= rect.MinX &&
x <= rect.MaxX &&
z >= rect.MinZ &&
z <= rect.MaxZ;
}
private static int TryParseEntityIndex(string? key)
{
if (string.IsNullOrWhiteSpace(key))
return -1;
var marker = ".entity.";
var markerIndex = key.LastIndexOf(marker, StringComparison.Ordinal);
if (markerIndex < 0)
return -1;
var start = markerIndex + marker.Length;
var end = start;
while (end < key.Length && char.IsDigit(key[end]))
end++;
return int.TryParse(key.Substring(start, end - start), out var value)
? value
: -1;
}
private static bool IntersectsBounds(
AetheriaRuntimeDaemonRenderGroupDocument group,
double minX,