mirror of
https://github.com/Facepunch/sbox-public.git
synced 2026-08-01 08:18:20 -04:00
// SkinnedModelRenderer - the only new entry point for speaking
SoundHandle SpeakSound( SoundEvent sound, GameObject mouth = null )
SoundHandle SpeakSound( SoundFile sound, float volume = 1, float pitch = 1, GameObject mouth = null )
// SoundFile - the authored lipsync track, read from the compiled vsnd
VisemeTrack Visemes { get; } // null = no track (or not loaded yet, see IsValidForPlayback)
// VisemeTrack - visemes over time, authored in the sound editor
IReadOnlyList<VisemeFrame> Frames { get; }
float Duration { get; }
bool Sample( float time, Span<float> weights )
// VisemeFrame - one viseme span (struct: Viseme, StartTime, EndTime)
// Visemes - the standard 15-viseme table, OVR order
static IReadOnlyList<string> MorphNames { get; }
static int Count { get; }
// MorphCollection - the one shared viseme blend (sounds, voice chat, editor preview)
void ApplyVisemes( ReadOnlySpan<float> visemeWeights, float scale = 1, float smoothTime = 0 )
// ModelMorphs - per-model viseme weight table, cached managed-side (no interop on lookups)
float GetVisemeMorph( int viseme, int morph )
float GetVisemeMorph( string viseme, int morph )
// ResourceCompileContext - inject managed data into natively-compiled resources
void WriteBlock( string blockName, ReadOnlySpan<byte> data ) // four-char block names, eg "LIPS"
void WriteBlockJson( string blockName, object obj )
JsonObject ReadMeta() // the asset's .meta, registers a compile dependency
T ReadMeta<T>( string key, T defaultValue = default )
// ResourceLoadContext - read blocks back when the resource loads (Resource.OnLoaded, engine-internal for now)
// public ref struct, only valid during the load callback
bool Exists( string blockName )
ReadOnlySpan<byte> ReadData( string blockName )
string ReadString( string blockName )
T ReadJson<T>( string blockName, T defaultValue = default )
195 lines
6.1 KiB
C#
195 lines
6.1 KiB
C#
using System.Text.Json.Serialization;
|
|
using static Sandbox.BytePack;
|
|
|
|
namespace Sandbox;
|
|
|
|
/// <summary>
|
|
/// A resource loaded in the engine, such as a <see cref="Model"/> or <see cref="Material"/>.
|
|
/// </summary>
|
|
[Expose]
|
|
public abstract partial class Resource : IValid, IJsonConvert, BytePack.ISerializer
|
|
{
|
|
/// <summary>
|
|
/// ID of this resource,
|
|
/// </summary>
|
|
[Hide, JsonIgnore]
|
|
[Obsolete( "ResourceId is obsolete and will be removed in the future." )]
|
|
public int ResourceId { get; protected set; }
|
|
|
|
/// Internal use only — a stable 64-bit hash of ResourcePath, used for networking and resource lookup.
|
|
internal ulong ResourceIdLong { get; set; }
|
|
|
|
/// <summary>
|
|
/// Path to this resource.
|
|
/// </summary>
|
|
[Hide, JsonIgnore]
|
|
public string ResourcePath { get; protected set; }
|
|
|
|
/// <summary>
|
|
/// File name of the resource without the extension.
|
|
/// </summary>
|
|
[Hide, JsonIgnore]
|
|
public string ResourceName { get; protected set; }
|
|
|
|
/// <summary>
|
|
/// This is what loads the resource. While this is alive the resource will be loaded.
|
|
/// </summary>
|
|
internal AsyncResourceLoader Manifest { get; set; }
|
|
|
|
|
|
[Hide, JsonIgnore] public abstract bool IsValid { get; }
|
|
[Hide, JsonIgnore] public virtual bool IsError => false;
|
|
|
|
/// <summary>
|
|
/// True if this resource has been changed but the changes aren't written to disk
|
|
/// </summary>
|
|
[Hide, JsonIgnore] public virtual bool HasUnsavedChanges => false;
|
|
|
|
internal virtual void Destroy()
|
|
{
|
|
// Unregister on main thread
|
|
// Null check because resource lirbary may already be gone.
|
|
MainThread.Queue( () => { Game.Resources?.Unregister( this ); } );
|
|
|
|
if ( Manifest != default ) MainThread.QueueDispose( Manifest );
|
|
Manifest = default;
|
|
|
|
GC.SuppressFinalize( this );
|
|
}
|
|
|
|
~Resource()
|
|
{
|
|
Destroy();
|
|
}
|
|
|
|
internal static string FixPath( string filename )
|
|
{
|
|
if ( filename == null )
|
|
return "";
|
|
|
|
filename = filename.NormalizeFilename( false );
|
|
if ( filename.EndsWith( "_c" ) ) filename = filename[..^2];
|
|
filename = filename.TrimStart( '/' );
|
|
|
|
return filename;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the ResourcePath, ResourceName and ResourceId from a resource path.
|
|
/// Registers in the WeakIndex so the resource can be found by ResourceId for networking,
|
|
/// without preventing garbage collection.
|
|
/// This is intended for runtime/native resources only. Disk-based resources (GameResource)
|
|
/// should use <see cref="ResourceSystem.Register"/> instead.
|
|
/// </summary>
|
|
internal void RegisterWeakResourceId( string resourcePath )
|
|
{
|
|
ResourcePath = FixPath( resourcePath );
|
|
ResourceName = System.IO.Path.GetFileNameWithoutExtension( ResourcePath );
|
|
|
|
#pragma warning disable CS0618 // Type or member is obsolete
|
|
ResourceId = ResourcePath.FastHash();
|
|
#pragma warning restore CS0618 // Type or member is obsolete
|
|
ResourceIdLong = ResourcePath.FastHash64();
|
|
|
|
Game.Resources.RegisterWeak( this );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Accessor for loading native resources, not great, doesn't need to handle GameResource
|
|
/// </summary>
|
|
internal static Resource Load( Type t, string filename )
|
|
{
|
|
if ( t == typeof( Material ) ) return Material.Load( filename );
|
|
if ( t == typeof( Texture ) ) return Texture.Load( filename );
|
|
if ( t == typeof( Model ) ) return Model.Load( filename );
|
|
if ( t == typeof( SoundFile ) ) return SoundFile.Load( filename );
|
|
if ( t == typeof( AnimationGraph ) ) return AnimationGraph.Load( filename );
|
|
if ( t == typeof( Shader ) ) return Shader.Load( filename );
|
|
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called by OnResourceReloaded when a resource has been reloaded
|
|
/// </summary>
|
|
internal virtual void OnReloaded()
|
|
{
|
|
}
|
|
|
|
internal static void OnResourceReloaded( string resourceName, IntPtr nativePointer )
|
|
{
|
|
Log.Trace( $"Resource Reloaded: '{resourceName}'" );
|
|
|
|
if ( NativeResourceCache.TryGetValue( nativePointer.ToInt64(), out Resource value ) )
|
|
{
|
|
Log.Trace( $" - '{value}'" );
|
|
value?.OnReloaded();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called when this resource's file data has been loaded (or reloaded), while
|
|
/// that data is still in memory. This is our chance to read anything we want
|
|
/// out of the compiled file - custom blocks written by managed resource
|
|
/// compilers, for example. The context is only valid during this call, so copy
|
|
/// out what you need. Main thread.
|
|
/// </summary>
|
|
internal virtual void OnLoaded( ResourceLoadContext context )
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called by the resource system when any resource's file data has been loaded,
|
|
/// while that data is still in memory. Almost all resource loads are initiated
|
|
/// from managed, so usually the wrapper already exists - find it by identity
|
|
/// and hand it the file data via <see cref="OnLoaded"/>. Runs on the main
|
|
/// thread, fires on reloads too.
|
|
/// </summary>
|
|
internal static void OnResourceLoaded( string resourceName, IntPtr header )
|
|
{
|
|
// This fires from the engine frame, outside any context scope - the wrapper
|
|
// could be registered in either context's resource system, so check both.
|
|
var resource = Engine.GlobalContext.Game.ResourceSystem.Get( typeof( Resource ), resourceName )
|
|
?? Engine.GlobalContext.Menu.ResourceSystem.Get( typeof( Resource ), resourceName );
|
|
|
|
resource?.OnLoaded( new ResourceLoadContext( resourceName, header ) );
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return $"{GetType().Name}:{ResourceName}";
|
|
}
|
|
|
|
/// <summary>
|
|
/// Should be called after the resource has been edited by the inspector
|
|
/// </summary>
|
|
public virtual void StateHasChanged()
|
|
{
|
|
|
|
}
|
|
|
|
static object BytePack.ISerializer.BytePackRead( ref ByteStream bs, Type targetType )
|
|
{
|
|
var id = bs.Read<ulong>();
|
|
// Fast path: already loaded in one of the indexes, garantueed for GameResources
|
|
var resource = Game.Resources.GetByIdLong<Resource>( id );
|
|
if ( resource != null ) return resource;
|
|
|
|
// Slow path: native resource exists on disk but hasn't been loaded yet (common on connecting clients).
|
|
var path = Game.Resources.LookupPath( id );
|
|
if ( path == null ) return null;
|
|
return Load( targetType, path );
|
|
}
|
|
|
|
static void BytePack.ISerializer.BytePackWrite( object value, ref ByteStream bs )
|
|
{
|
|
if ( value is not Resource resource )
|
|
{
|
|
bs.Write( 0UL );
|
|
return;
|
|
}
|
|
|
|
bs.Write( resource.ResourceIdLong );
|
|
}
|
|
}
|