442 lines
9.8 KiB
C#
442 lines
9.8 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
//using System.Threading.Tasks;
|
|
using System.Diagnostics;
|
|
using System.Reflection;
|
|
using System.Collections.Immutable;
|
|
using System.Threading;
|
|
using System.IO;
|
|
using Microsoft.CodeAnalysis;
|
|
using System.Runtime.CompilerServices;
|
|
|
|
|
|
namespace res;
|
|
|
|
|
|
using ImmDefLoad = ImmutableQueue<(string name, Ref)>;
|
|
|
|
public interface Res_old
|
|
{
|
|
|
|
}
|
|
|
|
[DebuggerDisplay("Path = {path}")]
|
|
public class Ref : lib.I_Serialize
|
|
{
|
|
static public bool s_verboseLogging = false;
|
|
|
|
public string Filename =>path;
|
|
|
|
|
|
public Ref( string filename = "{empty_filename}",
|
|
string reason = "",
|
|
[CallerMemberName] string dbgName = "",
|
|
[CallerFilePath] string dbgPath = "",
|
|
[CallerLineNumber] int dbgLine = 0
|
|
)
|
|
{
|
|
path = filename;
|
|
if( s_verboseLogging ) log.info( $"Ref: {GetType().Name} {path}" );
|
|
|
|
_reason = reason;
|
|
_dbgName = dbgName;
|
|
_dbgPath = dbgPath;
|
|
_dbgLine = dbgLine;
|
|
}
|
|
|
|
virtual public object lookup(
|
|
string reason = "",
|
|
[CallerMemberName] string dbgName = "",
|
|
[CallerFilePath] string dbgPath = "",
|
|
[CallerLineNumber] int dbgLine = 0
|
|
|
|
) => default;
|
|
|
|
|
|
virtual public void OnChange()
|
|
{
|
|
}
|
|
|
|
virtual internal void load()
|
|
{
|
|
|
|
}
|
|
|
|
private string path = "{set_from_inline_cons}";
|
|
|
|
protected string _reason = "";
|
|
protected string _dbgName = "";
|
|
protected string _dbgPath = "";
|
|
protected int _dbgLine = 0;
|
|
protected string _dbgExp = "";
|
|
|
|
|
|
}
|
|
|
|
[Serializable]
|
|
[DebuggerDisplay("Path = {path} / Res = {res}")]
|
|
public class Ref<T> : Ref where T : class, new()
|
|
{
|
|
public T res => m_res != null ? m_res : lookup();
|
|
|
|
override public T lookup(
|
|
string reason = "",
|
|
[CallerMemberName] string dbgName = "",
|
|
[CallerFilePath] string dbgPath = "",
|
|
[CallerLineNumber] int dbgLine = 0
|
|
|
|
)
|
|
{
|
|
m_res = Mgr.load<T>( Filename, $"Ref lookup", dbgName, dbgPath, dbgLine );
|
|
if( s_verboseLogging ) log.info( $"Ref.lookup {GetType().Name} {GetType().GenericTypeArguments[0]} path {Filename}" );
|
|
return m_res;
|
|
}
|
|
|
|
//For serialization
|
|
/*
|
|
public Ref()
|
|
:
|
|
base( "{set_from_ref<>_default_cons}" )
|
|
{
|
|
if( s_verboseLogging ) log.info( $"Ref {GetType().Name} {GetType().GenericTypeArguments[0]} path {Filename}" );
|
|
}
|
|
*/
|
|
|
|
|
|
public Ref( string filename = "",
|
|
string reason = "",
|
|
[CallerMemberName] string dbgName = "",
|
|
[CallerFilePath] string dbgPath = "",
|
|
[CallerLineNumber] int dbgLine = 0
|
|
)
|
|
:
|
|
base( !string.IsNullOrWhiteSpace(filename) ? filename : $"{{{dbgName}_{log.whatFile(dbgPath)}}}" , reason, dbgName, dbgPath, dbgLine )
|
|
{
|
|
if( s_verboseLogging ) log.info( $"Ref {GetType().Name} {GetType().GenericTypeArguments[0]} path {Filename}" );
|
|
}
|
|
|
|
|
|
override internal void load()
|
|
{
|
|
m_res = Mgr.load<T>( Filename, _reason, _dbgName, _dbgPath, _dbgLine, _dbgExp );
|
|
if( s_verboseLogging ) log.info( $"Ref.load {GetType().Name} {GetType().GenericTypeArguments[0]} path {Filename}" );
|
|
}
|
|
|
|
public object OnDeserialize( object enclosing )
|
|
{
|
|
return enclosing;
|
|
}
|
|
|
|
static public Ref<T> createAsset( T v, string path,
|
|
string reason = "",
|
|
[CallerMemberName] string dbgName = "",
|
|
[CallerFilePath] string dbgPath = "",
|
|
[CallerLineNumber] int dbgLine = 0
|
|
)
|
|
{
|
|
if( File.Exists( path ) )
|
|
{
|
|
log.warn( $"For {typeof(T).Name}, saving asset to {path}, but it already exists" );
|
|
|
|
var newPath = $"{path}_{DateTime.Now.ToShortDateString()}_{DateTime.Now.ToShortTimeString()}";
|
|
|
|
System.IO.File.Move(path, newPath );
|
|
|
|
log.warn( $"For {typeof(T).Name}, renamed to {newPath}" );
|
|
}
|
|
|
|
var createReason = $"Type {v.GetType().Name}";
|
|
|
|
if( v is imm.Imm imm )
|
|
{
|
|
createReason = $"{imm?.Meta}";
|
|
}
|
|
|
|
var newRef = new Ref<T>( path, $"createAsset {createReason} bcs {reason}", dbgName, dbgPath, dbgLine );
|
|
|
|
return newRef;
|
|
}
|
|
|
|
[NonSerialized]
|
|
protected T m_res;
|
|
}
|
|
|
|
public class Resource
|
|
{
|
|
static public Mgr mgr;
|
|
}
|
|
|
|
public delegate T Load<out T>( string filename );
|
|
|
|
|
|
class LoadHolder
|
|
{
|
|
internal virtual object load()
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
|
|
class LoadHolder<T> : LoadHolder
|
|
{
|
|
public LoadHolder( Load<T> fnLoad )
|
|
{
|
|
_fnLoad = fnLoad;
|
|
}
|
|
|
|
public Load<T> _fnLoad;
|
|
|
|
internal override object load()
|
|
{
|
|
return load();
|
|
}
|
|
}
|
|
|
|
public record class ResourceHolder<T>( WeakReference<T> weak, string Name, DateTime captured ) : imm.Recorded<ResourceHolder<T>>
|
|
where T : class
|
|
{
|
|
|
|
}
|
|
|
|
//generic classes make a new static per generic type
|
|
class ResCache<T> where T : class, new()
|
|
{
|
|
public static T s_default = new();
|
|
public static ImmutableDictionary<string, ResourceHolder<T>> s_cache = ImmutableDictionary<string, ResourceHolder<T>>.Empty;
|
|
}
|
|
|
|
|
|
public class Mgr
|
|
{
|
|
static public void startup()
|
|
{
|
|
Resource.mgr = new Mgr();
|
|
}
|
|
|
|
static public void register<T>( Load<T> loader )
|
|
{
|
|
Debug.Assert( !Resource.mgr.m_loaders.ContainsKey( typeof( T ) ) );
|
|
|
|
var lh = new LoadHolder<T>( loader );
|
|
|
|
ImmutableInterlocked.TryAdd( ref Resource.mgr.m_loaders, typeof( T ), lh );
|
|
}
|
|
|
|
//Register all subclasses of a particular type
|
|
//???? Should we just always do this?
|
|
static public void registerSub<T>()
|
|
{
|
|
registerSub( typeof(T) );
|
|
}
|
|
|
|
static public void registerSub( Type baseType )
|
|
{
|
|
log.info( $"Registering loader for {baseType.Name}" );
|
|
|
|
Type[] typeParams = new Type[1];
|
|
foreach( var mi in baseType.GetMethods() )
|
|
{
|
|
if( mi.Name == "res_load" && mi.IsGenericMethod )
|
|
{
|
|
foreach( var ass in AppDomain.CurrentDomain.GetAssemblies() )
|
|
{
|
|
foreach( var t in ass.GetTypes() )
|
|
{
|
|
if( !baseType.IsAssignableFrom( t ) )
|
|
continue;
|
|
|
|
log.debug( $"Making a loader for {t.Name}" );
|
|
|
|
typeParams[0] = t;
|
|
var mi_ng = mi.MakeGenericMethod( typeParams );
|
|
|
|
var loadGenType = typeof( Load<> );
|
|
|
|
var loadType = loadGenType.MakeGenericType( t );
|
|
|
|
var loader = Delegate.CreateDelegate( loadType, mi_ng );
|
|
|
|
var lhGenType = typeof( LoadHolder<> );
|
|
|
|
var lhType = lhGenType.MakeGenericType( t );
|
|
|
|
var lh = Activator.CreateInstance( lhType, loader ) as LoadHolder;
|
|
|
|
ImmutableInterlocked.TryAdd( ref Resource.mgr.m_loaders, t, lh );
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static public Ref<T> lookup<T>( string filename,
|
|
string reason = "",
|
|
[CallerMemberName] string dbgName = "",
|
|
[CallerFilePath] string dbgPath = "",
|
|
[CallerLineNumber] int dbgLine = 0
|
|
) where T : class, new()
|
|
{
|
|
return new Ref<T>( filename, reason, dbgName, dbgPath, dbgLine );
|
|
}
|
|
|
|
/*
|
|
|
|
static public Ref lookup( string filename, Type t,
|
|
string reason = "",
|
|
[CallerMemberName] string dbgName = "",
|
|
[CallerFilePath] string dbgPath = "",
|
|
[CallerLineNumber] int dbgLine = 0
|
|
)
|
|
{
|
|
return new Ref( filename, reason, dbgName, dbgPath, dbgLine );
|
|
}
|
|
|
|
// @@@ TODO Pass information through here
|
|
static public T? load<T>( string filename ) where T : class
|
|
{
|
|
if( ResCache<T>.s_cache.TryGetValue( filename, out var holder ) )
|
|
{
|
|
|
|
if( holder.weak.TryGetTarget( out var v ) )
|
|
{
|
|
return v;
|
|
}
|
|
|
|
log.info( $"{filename} was in cache, but its been dropped, reloading." );
|
|
}
|
|
|
|
log.warn( $"Block Loading {filename}." );
|
|
|
|
var newV = actualLoad<T>( filename );
|
|
|
|
return newV;
|
|
}
|
|
*/
|
|
|
|
static public T? load<T>( string filename,
|
|
string reason = "",
|
|
[CallerMemberName] string dbgName = "",
|
|
[CallerFilePath] string dbgPath = "",
|
|
[CallerLineNumber] int dbgLine = 0,
|
|
[CallerArgumentExpression("fn")]
|
|
string dbgExp = default
|
|
) where T : class, new()
|
|
{
|
|
if( ResCache<T>.s_cache.TryGetValue( filename, out var holder ) )
|
|
{
|
|
if( holder.weak.TryGetTarget( out var v ) )
|
|
{
|
|
return v;
|
|
}
|
|
|
|
log.info( $"{filename} was in cache, but its been dropped, reloading." );
|
|
}
|
|
|
|
log.warn( $"Block Loading {filename}." );
|
|
|
|
var newV = actualLoad<T>( filename );
|
|
|
|
if( newV is imm.Imm imm )
|
|
{
|
|
newV = (T)imm.Record( $"Loading because {reason}", dbgName, dbgPath, dbgLine );
|
|
}
|
|
|
|
return newV;
|
|
}
|
|
|
|
static public T actualLoad<T>( string filename ) where T : class, new()
|
|
{
|
|
lock(s_loading)
|
|
{
|
|
if( s_loading.TryGetValue( filename, out var evt ) )
|
|
{
|
|
if( ResCache<T>.s_cache.TryGetValue( filename, out var holder ) )
|
|
{
|
|
if( holder.weak.TryGetTarget( out var v ) )
|
|
{
|
|
log.trace( $"{typeof(T).Name} loading {filename}" );
|
|
return v;
|
|
}
|
|
|
|
log.error( $"{filename} was in cache, but its been dropped, reloading." );
|
|
}
|
|
}
|
|
|
|
{
|
|
if( Resource.mgr.m_loaders.TryGetValue( typeof( T ), out var loaderGen ) )
|
|
{
|
|
var loader = loaderGen as LoadHolder<T>;
|
|
|
|
var v = loader._fnLoad( filename );
|
|
|
|
var weak = new WeakReference<T>( v );
|
|
|
|
var holder = new ResourceHolder<T>( weak, $"", DateTime.Now ).Record();
|
|
|
|
log.info( $"To {typeof(T).Name} add {filename}" );
|
|
|
|
var alreadyAdded = !ImmutableInterlocked.TryAdd( ref ResCache<T>.s_cache, filename, holder );
|
|
|
|
if( alreadyAdded )
|
|
{
|
|
log.error( $"Key {filename} already existed, though it shouldnt." );
|
|
}
|
|
|
|
return v;
|
|
}
|
|
else
|
|
{
|
|
log.error( $"Loader could not be found for type {typeof( T )}" );
|
|
|
|
return ResCache<T>.s_default;
|
|
}
|
|
}
|
|
}
|
|
|
|
return actualLoad<T>( filename );
|
|
}
|
|
|
|
static object s_loadingLock = new object();
|
|
|
|
static ImmutableDictionary<string, AutoResetEvent> s_loading = ImmutableDictionary<string, AutoResetEvent>.Empty;
|
|
static ImmDefLoad s_deferredLoad = ImmDefLoad.Empty;
|
|
|
|
|
|
Mgr()
|
|
{
|
|
log.info( $"Creating Res.Mgr" );
|
|
|
|
var ts = new ThreadStart( deferredLoader );
|
|
|
|
m_deferredLoader = new Thread( ts );
|
|
|
|
m_deferredLoader.Start();
|
|
}
|
|
|
|
void deferredLoader()
|
|
{
|
|
while( true )
|
|
{
|
|
Thread.Sleep( 1 );
|
|
|
|
if( ImmutableInterlocked.TryDequeue( ref s_deferredLoad, out var v ) )
|
|
{
|
|
v.Item2.load();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
ImmutableDictionary<Type, LoadHolder> m_loaders = ImmutableDictionary<Type, LoadHolder>.Empty;
|
|
|
|
Thread m_deferredLoader;
|
|
|
|
}
|
|
|