mirror of
https://github.com/Facepunch/sbox-public.git
synced 2026-01-14 17:29:23 -05:00
This commit imports the C# engine code and game files, excluding C++ source code. [Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
270 lines
7.0 KiB
C#
270 lines
7.0 KiB
C#
using Sandbox.Internal;
|
|
using Sandbox.Utility;
|
|
using System.Collections.Concurrent;
|
|
using System.Text.Json.Nodes;
|
|
using System.Text.Json.Serialization;
|
|
using System.Threading;
|
|
|
|
namespace Sandbox.Resources;
|
|
|
|
/// <summary>
|
|
/// Creates a resource from a json definition
|
|
/// </summary>
|
|
[Expose]
|
|
public abstract class ResourceGenerator
|
|
{
|
|
public struct Options
|
|
{
|
|
/// <summary>
|
|
/// True if we're compiling this resource to write to disk
|
|
/// </summary>
|
|
public bool ForDisk { get; set; }
|
|
|
|
/// <summary>
|
|
/// Will be set to the compiler that is currently compiling this resource. Or null, if we're generating in another method.
|
|
/// </summary>
|
|
public ResourceCompiler Compiler { get; set; }
|
|
|
|
public static Options Default => new Options { };
|
|
}
|
|
|
|
/// <summary>
|
|
/// If true then the generation will create a real resource and store it on disk.
|
|
/// Use this if creating the resource takes a while, or you won't be shipping the generator
|
|
/// with the game, or if it relies on data that won't be available in the shipped game.
|
|
/// </summary>
|
|
[JsonIgnore, Hide]
|
|
public virtual bool CacheToDisk => false;
|
|
|
|
/// <summary>
|
|
/// Create a ResourceGenerator by name
|
|
/// </summary>
|
|
public static ResourceGenerator<T> Create<T>( string generatorName ) where T : Resource
|
|
{
|
|
var typeLibrary = TypeLibrary.Editor ?? Game.TypeLibrary;
|
|
if ( typeLibrary is null ) return default;
|
|
|
|
var t = typeLibrary.GetType<ResourceGenerator<T>>( generatorName );
|
|
if ( t == null ) return default;
|
|
|
|
return t.Create<ResourceGenerator<T>>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a ResourceGenerator by name and deserialize it
|
|
/// </summary>
|
|
public static ResourceGenerator<T> Create<T>( EmbeddedResource serialized ) where T : Resource
|
|
{
|
|
// find the generator name
|
|
if ( string.IsNullOrEmpty( serialized.ResourceGenerator ) ) return default;
|
|
|
|
// create the generator type
|
|
var generator = Create<T>( serialized.ResourceGenerator );
|
|
if ( generator is null ) return default;
|
|
|
|
// fill it with json
|
|
generator.Deserialize( serialized.Data );
|
|
|
|
return generator;
|
|
}
|
|
|
|
public static T CreateResource<T>( EmbeddedResource obj, Options options ) where T : Resource
|
|
{
|
|
// create the generator type
|
|
var generator = Create<T>( obj );
|
|
if ( generator is null ) return default;
|
|
|
|
// create the resource
|
|
return generator.FindOrCreate( options );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a resource from an embedded resource with a given <see cref="Type"/>
|
|
/// </summary>
|
|
/// <param name="obj"></param>
|
|
/// <param name="options"></param>
|
|
/// <param name="type"></param>
|
|
/// <returns></returns>
|
|
public static Resource CreateResource( EmbeddedResource obj, Options options, Type type )
|
|
{
|
|
var typeLibrary = TypeLibrary.Editor ?? Game.TypeLibrary;
|
|
if ( typeLibrary is null ) return default;
|
|
|
|
var t = typeLibrary.GetType<ResourceGenerator>( obj.ResourceGenerator );
|
|
if ( t == null ) return default;
|
|
|
|
var generator = t.Create<ResourceGenerator>();
|
|
|
|
// fill it with json
|
|
generator.Deserialize( obj.Data );
|
|
|
|
// create the resource
|
|
return generator.FindOrCreateObject( options ) as Resource;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Copy properties from obj to us
|
|
/// </summary>
|
|
public virtual void Deserialize( JsonObject obj )
|
|
{
|
|
if ( obj is null ) return;
|
|
|
|
Json.DeserializeToObject( this, obj );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a hash to be used when loading/saving. We use this to determine if the resource has changed.
|
|
/// By default we serialize the generator to a json string and return the CRC64 of that value. You can
|
|
/// override this in your generator if you need to make it faster, or ignore some stuff.
|
|
/// </summary>
|
|
public virtual ulong GetHash()
|
|
{
|
|
var jsonString = Json.Serialize( this );
|
|
return Crc64.FromString( jsonString );
|
|
}
|
|
|
|
/// <summary>
|
|
/// If we generated this before, then find the current cache'd value.
|
|
/// If not, then generate a new one.
|
|
/// </summary>
|
|
public abstract ValueTask<Resource> FindOrCreateObjectAsync( Options options, CancellationToken token );
|
|
|
|
/// <summary>
|
|
/// Find or create the resource (blocking)
|
|
/// </summary>
|
|
/// <param name="options"></param>
|
|
/// <returns></returns>
|
|
public abstract Resource FindOrCreateObject( Options options );
|
|
}
|
|
|
|
/// <summary>
|
|
/// A resource generator targetting a specific type
|
|
/// </summary>
|
|
public abstract class ResourceGenerator<T> : ResourceGenerator where T : Resource
|
|
{
|
|
static WeakDictionary<ulong, T> cache = new();
|
|
|
|
/// <summary>
|
|
/// If true then the generation will avoid creating duplicate resources by checking
|
|
/// hash codes of previously generated resources and re-using them if possible.
|
|
/// </summary>
|
|
[Hide, JsonIgnore]
|
|
public virtual bool UseMemoryCache => true;
|
|
|
|
/// <summary>
|
|
/// Find a previously created of this resource
|
|
/// </summary>
|
|
public virtual T FindCached()
|
|
{
|
|
if ( !UseMemoryCache )
|
|
return default;
|
|
|
|
var hash = GetHash();
|
|
|
|
if ( cache.TryGetValue( hash, out var value ) )
|
|
{
|
|
return value;
|
|
}
|
|
|
|
return default;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add this resource to the cache for our current hash
|
|
/// </summary>
|
|
public void AddToCache( T val )
|
|
{
|
|
if ( !UseMemoryCache ) return;
|
|
if ( val == default ) return;
|
|
|
|
cache.Set( GetHash(), val );
|
|
}
|
|
|
|
/// <summary>
|
|
/// If we generated this before, then find the current cache'd value.
|
|
/// If not, then generate a new one.
|
|
/// </summary>
|
|
public virtual T FindOrCreate( Options options )
|
|
{
|
|
if ( FindCached() is { } cached )
|
|
return cached;
|
|
|
|
var x = Create( options );
|
|
AddToCache( x );
|
|
return x;
|
|
}
|
|
|
|
public override async ValueTask<Resource> FindOrCreateObjectAsync( Options options, CancellationToken token )
|
|
=> await FindOrCreateAsync( options, token );
|
|
|
|
public override Resource FindOrCreateObject( Options options ) => FindOrCreate( options );
|
|
|
|
/// <summary>
|
|
/// If we generated this before, then find the current cache'd value.
|
|
/// If not, then generate a new one.
|
|
/// </summary>
|
|
public virtual async ValueTask<T> FindOrCreateAsync( Options options, CancellationToken token )
|
|
{
|
|
if ( FindCached() is { } cached )
|
|
return cached;
|
|
|
|
var hash = GetHash();
|
|
var v = await CreateAsync( options, token );
|
|
|
|
// If the cache value changed while we were generating
|
|
// then throw this value away!
|
|
if ( hash == GetHash() )
|
|
{
|
|
AddToCache( v );
|
|
}
|
|
|
|
return v;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create the resource blocking
|
|
/// </summary>
|
|
public abstract T Create( Options options );
|
|
|
|
/// <summary>
|
|
/// Create the resource asyncronously
|
|
/// </summary>
|
|
public abstract ValueTask<T> CreateAsync( Options options, CancellationToken token );
|
|
}
|
|
|
|
|
|
class WeakDictionary<TKey, TValue> where TValue : class
|
|
{
|
|
private readonly ConcurrentDictionary<TKey, WeakReference<TValue>> _table = new();
|
|
|
|
public void Set( TKey key, TValue value )
|
|
{
|
|
var weakValue = new WeakReference<TValue>( value );
|
|
_table[key] = weakValue;
|
|
Cleanup();
|
|
}
|
|
|
|
public bool TryGetValue( TKey key, out TValue value )
|
|
{
|
|
Cleanup();
|
|
|
|
value = default;
|
|
|
|
if ( !_table.TryGetValue( key, out var weak ) )
|
|
return false;
|
|
|
|
return weak.TryGetTarget( out value );
|
|
}
|
|
|
|
private void Cleanup()
|
|
{
|
|
foreach ( var kvp in _table )
|
|
{
|
|
if ( !kvp.Value.TryGetTarget( out _ ) )
|
|
{
|
|
_table.TryRemove( kvp.Key, out _ );
|
|
}
|
|
}
|
|
}
|
|
}
|