using System.Text.Json.Serialization;
namespace Sandbox;
#nullable enable
///
/// A serialized reference to a that can be resolved at runtime.
/// Components are referenced by their , their containing object's
/// , and their if available.
///
[ActionGraphExposeWhenCached]
internal readonly struct ComponentReference
{
private const string ExpectedReferenceType = "component";
///
/// Reference a given .
///
public static ComponentReference FromInstance( Component component )
{
var t = Game.TypeLibrary.GetType( component.GetType() );
return new ComponentReference( ExpectedReferenceType, component.Id, component.GameObject.Id, t?.ClassName );
}
///
/// Converts a into a , referencing the object
/// that contains the component.
///
public static explicit operator GameObjectReference( ComponentReference componentRef )
{
return GameObjectReference.FromId( componentRef.GameObjectId );
}
///
/// Expected to be "component" for a component reference.
///
[JsonPropertyName( "_type" )]
public string ReferenceType { get; }
///
/// The of the referenced component.
///
[JsonPropertyName( "component_id" )]
public Guid ComponentId { get; }
///
/// The of the object containing the referenced component.
///
[JsonPropertyName( "go" )]
public Guid GameObjectId { get; }
///
/// If available, the of the referenced component.
///
[JsonPropertyName( "component_type" )]
public string? ComponentTypeName { get; }
[JsonConstructor]
private ComponentReference( string referenceType, Guid componentId, Guid gameObjectId, string? componentTypeName = null )
{
ReferenceType = referenceType;
ComponentId = componentId;
GameObjectId = gameObjectId;
ComponentTypeName = componentTypeName;
}
///
/// Attempt to resolve into a . Returns if not resolved.
///
/// Optional base type / interface that the resolved type must derive from / implement. Defaults to .
public Type? ResolveComponentType( Type? targetType = null )
{
return Game.TypeLibrary.GetType( targetType ?? typeof( Component ), ComponentTypeName, true )?.TargetType;
}
///
/// Attempt to resolve this reference in the current . Returns if
/// the reference couldn't be resolved, and logs a warning.
///
public Component? Resolve() => Resolve( Game.ActiveScene );
///
/// Attempt to resolve this reference in the given . Returns if
/// the reference couldn't be resolved.
///
/// Scene to attempt to resolve the reference in.
/// Optional base type / interface that the resolved instance must derive from / implement. Defaults to .
/// If true, log a warning to the console if the reference couldn't be resolved.
public Component? Resolve( Scene scene, Type? targetType = null, bool warn = false )
{
if ( ReferenceType != ExpectedReferenceType )
throw new( $"Tried to deserialize unknown type '{ReferenceType ?? "null"}' as Component" );
// TODO: Do we want to throw when not found? We're not doing that in GameObjectReference.Resolve
targetType ??= typeof( Component );
Component? component = null;
if ( ComponentId != Guid.Empty )
{
if ( !scene.IsValid() )
{
if ( warn ) Log.Warning( "Tried to read component - but active scene was null!" );
return null;
}
component = scene.Directory.FindComponentByGuid( ComponentId );
if ( component is not null )
return component;
}
if ( GameObjectId != Guid.Empty )
{
if ( !scene.IsValid() )
{
if ( warn ) Log.Warning( "Tried to read component - but active scene was null!" );
return null;
}
var go = scene.Directory.FindByGuid( GameObjectId );
if ( !go.IsValid() )
{
// Conna: this is okay - we may be deserializing over the network and this is a property
// referring to a GameObject that we don't know about. Let's just make the reference null.
if ( warn ) Log.Warning( $"Unknown GameObject {GameObjectId}" );
return null;
}
if ( !string.IsNullOrWhiteSpace( ComponentTypeName ) )
{
if ( ResolveComponentType( targetType ) is not { } resolvedType )
throw new( $"Unable to find type '{ComponentTypeName}' with base type '{targetType.Name}'" );
targetType = resolvedType;
}
component = go.Components.Get( targetType, FindMode.EverythingInSelf );
if ( component is not null )
return component;
throw new( $"Component '{go}:{targetType}' was not found" );
}
throw new( $"Component '{ComponentId}' was not found" );
}
}