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" ); } }