2026-07-12 12:00:59 +01:00
using System ;
using System.Collections.Generic ;
using System.Linq ;
using GameCult.Eve.Surface ;
2026-07-13 18:01:12 +01:00
using GameCult.Mesh ;
2026-07-12 12:00:59 +01:00
using UnityEngine ;
#nullable enable
namespace GameCult.Eve.UnityScene
{
public sealed class EveUnityEntitySoaView : IDisposable
{
2026-07-13 18:01:12 +01:00
private readonly ICultMeshBodyReadLease _lease ;
private readonly Dictionary < string , EveEntitySoaBuffer > _buffers ;
2026-07-12 12:00:59 +01:00
private readonly Dictionary < string , EveEntitySoaColumn > _columns ;
2026-07-19 05:56:09 +01:00
private readonly string [ ] _floatSemantics ;
2026-07-12 12:00:59 +01:00
2026-07-13 18:01:12 +01:00
private EveUnityEntitySoaView ( EveEntitySoaViewDocument document , ICultMeshBodyReadLease lease )
2026-07-12 12:00:59 +01:00
{
Document = document ;
2026-07-13 18:01:12 +01:00
_lease = lease ;
_buffers = document . Buffers . ToDictionary ( buffer = > buffer . BufferId , StringComparer . Ordinal ) ;
2026-07-12 12:00:59 +01:00
_columns = document . Columns . ToDictionary ( column = > column . Semantic , StringComparer . Ordinal ) ;
2026-07-19 05:56:09 +01:00
_floatSemantics = document . Columns
. Where ( column = > string . Equals ( column . ScalarType , "float32" , StringComparison . Ordinal ) )
. Select ( column = > column . Semantic )
. Where ( semantic = > ! string . IsNullOrWhiteSpace ( semantic ) )
. ToArray ( ) ;
2026-07-12 12:00:59 +01:00
}
public EveEntitySoaViewDocument Document { get ; }
2026-07-13 18:01:12 +01:00
public long Generation = > Document . Sequence ;
2026-07-12 12:00:59 +01:00
public IReadOnlyList < EveEntityRenderGroup > RenderGroups = > Document . RenderGroups ;
2026-07-13 18:01:12 +01:00
public int EntityCount = > Document . Columns . Length = = 0 ? 0 : Document . Columns . Min ( column = > column . ElementCount ) ;
2026-07-19 05:56:09 +01:00
public IReadOnlyList < string > FloatSemantics = > _floatSemantics ;
2026-07-12 12:00:59 +01:00
2026-07-13 18:01:12 +01:00
public static EveUnityEntitySoaView Open ( EveEntitySoaViewDocument document , ICultMeshBodyReadLease lease )
2026-07-12 12:00:59 +01:00
{
if ( document = = null ) throw new ArgumentNullException ( nameof ( document ) ) ;
2026-07-13 18:01:12 +01:00
if ( lease = = null ) throw new ArgumentNullException ( nameof ( lease ) ) ;
Validate ( document , lease ) ;
return new EveUnityEntitySoaView ( document , lease ) ;
2026-07-12 12:00:59 +01:00
}
public bool TryReadVector3 ( string semantic , int index , out Vector3 value )
{
value = default ;
2026-07-13 18:01:12 +01:00
if ( ! TryColumn ( semantic , index , "float3" , 12 , out var offset ) ) return false ;
value = new Vector3 ( _lease . ReadSingle ( offset ) , _lease . ReadSingle ( offset + 4 ) , _lease . ReadSingle ( offset + 8 ) ) ;
2026-07-12 12:00:59 +01:00
return true ;
}
public bool TryReadFloat ( string semantic , int index , out float value )
{
value = default ;
2026-07-13 18:01:12 +01:00
if ( ! TryColumn ( semantic , index , "float32" , 4 , out var offset ) ) return false ;
value = _lease . ReadSingle ( offset ) ;
2026-07-12 12:00:59 +01:00
return true ;
}
public bool TryReadInt32 ( string semantic , int index , out int value )
{
value = default ;
2026-07-13 18:01:12 +01:00
if ( ! TryColumn ( semantic , index , "int32" , 4 , out var offset ) ) return false ;
value = _lease . ReadInt32 ( offset ) ;
2026-07-12 12:00:59 +01:00
return true ;
}
2026-07-12 12:33:36 +01:00
public bool TryReadUInt32 ( string semantic , int index , out uint value )
{
value = default ;
2026-07-13 18:01:12 +01:00
if ( ! TryColumn ( semantic , index , "uint32" , 4 , out var offset ) ) return false ;
value = unchecked ( ( uint ) _lease . ReadInt32 ( offset ) ) ;
2026-07-12 12:33:36 +01:00
return true ;
}
2026-07-12 12:00:59 +01:00
public bool TryReadByte ( string semantic , int index , out byte value )
{
value = default ;
2026-07-13 18:01:12 +01:00
if ( ! TryColumn ( semantic , index , "uint8" , 1 , out var offset ) ) return false ;
value = _lease . ReadByte ( offset ) ;
2026-07-12 12:00:59 +01:00
return true ;
}
2026-07-13 18:01:12 +01:00
public void Dispose ( ) = > _lease . Dispose ( ) ;
private static void Validate ( EveEntitySoaViewDocument document , ICultMeshBodyReadLease lease )
2026-07-12 12:00:59 +01:00
{
2026-07-13 18:01:12 +01:00
if ( ! string . Equals ( document . Schema , EveEntitySoaViewDocument . SchemaId , StringComparison . Ordinal ) )
throw new ArgumentException ( $"Expected {EveEntitySoaViewDocument.SchemaId}, got '{document.Schema}'." , nameof ( document ) ) ;
if ( document . Buffers = = null | | document . Buffers . Length = = 0 )
throw new ArgumentException ( "Entity SoA view requires a primary logical buffer." , nameof ( document ) ) ;
var descriptor = lease . Descriptor ? ? throw new ArgumentException ( "Body lease has no descriptor." , nameof ( lease ) ) ;
var primary = document . Buffers [ 0 ] ;
if ( ! string . Equals ( descriptor . BodyId , primary . BufferId , StringComparison . Ordinal ) | |
! string . Equals ( descriptor . SchemaId , document . BodySchemaId , StringComparison . Ordinal ) | |
descriptor . LayoutVersion ! = document . LayoutVersion | |
descriptor . ProducerEpoch ! = document . ProducerEpoch | |
descriptor . Sequence ! = document . Sequence | |
descriptor . Capacity ! = document . Capacity )
throw new InvalidOperationException ( "CultMesh body lease generation does not match the Eve entity layout." ) ;
if ( document . Capacity < 0 | | document . Buffers . Any ( buffer = >
string . IsNullOrWhiteSpace ( buffer . BufferId ) | | buffer . ByteOffset < 0 | | buffer . ByteLength < 0 | |
buffer . ByteOffset > descriptor . ByteSize - buffer . ByteLength ) )
throw new InvalidOperationException ( "Eve entity layout contains an invalid logical buffer range." ) ;
var buffers = document . Buffers . ToDictionary ( buffer = > buffer . BufferId , StringComparer . Ordinal ) ;
foreach ( var column in document . Columns ? ? Array . Empty < EveEntitySoaColumn > ( ) )
{
if ( ! buffers . TryGetValue ( column . BufferId , out var buffer ) | | column . ByteOffset < 0 | |
column . ElementStride < = 0 | | column . ElementCount < 0 | | column . ElementCount > document . Capacity )
throw new InvalidOperationException ( $"Eve entity column '{column.ColumnId}' has invalid layout metadata." ) ;
var width = ScalarWidth ( column . ScalarType ) ;
var occupied = column . ElementCount = = 0 ? 0 : ( long ) ( column . ElementCount - 1 ) * column . ElementStride + width ;
if ( width = = 0 | | column . ByteOffset > buffer . ByteLength - occupied )
throw new InvalidOperationException ( $"Eve entity column '{column.ColumnId}' exceeds its logical buffer." ) ;
}
2026-07-12 12:00:59 +01:00
}
2026-07-13 18:01:12 +01:00
private bool TryColumn ( string semantic , int index , string scalarType , int minimumStride , out long offset )
2026-07-12 12:00:59 +01:00
{
2026-07-13 18:01:12 +01:00
offset = 0 ;
if ( index < 0 | | ! _columns . TryGetValue ( semantic ? ? "" , out var column ) ) return false ;
2026-07-12 12:00:59 +01:00
if ( index > = column . ElementCount | | column . ElementStride < minimumStride ) return false ;
if ( ! string . Equals ( column . ScalarType , scalarType , StringComparison . Ordinal ) ) return false ;
2026-07-13 18:01:12 +01:00
if ( ! _buffers . TryGetValue ( column . BufferId , out var buffer ) ) return false ;
offset = buffer . ByteOffset + column . ByteOffset + ( long ) index * column . ElementStride ;
return true ;
2026-07-12 12:00:59 +01:00
}
2026-07-13 18:01:12 +01:00
private static int ScalarWidth ( string scalarType ) = > scalarType switch
{
"float3" = > 12 ,
"float32" = > 4 ,
"int32" = > 4 ,
"uint32" = > 4 ,
"uint8" = > 1 ,
_ = > 0
} ;
2026-07-12 12:00:59 +01:00
}
}