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; #nullable enable namespace res; using ImmDefLoad = ImmutableQueue<(string name, Ref)>; public interface Res_old { } [DebuggerDisplay("Path = {path}")] abstract 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; } abstract public object lookup( string reason = "", [CallerMemberName] string dbgName = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = 0 ); 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 : 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( 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}" ); m_res = default; } override internal void load() { m_res = Mgr.load( Filename, _reason, _dbgName, _dbgPath, _dbgLine ); if( s_verboseLogging ) log.info( $"Ref.load {GetType().Name} {GetType().GenericTypeArguments[0]} path {Filename}" ); } public object OnDeserialize( object enclosing ) { return enclosing; } static public Ref 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( path, $"createAsset {createReason} bcs {reason}", dbgName, dbgPath, dbgLine ); return newRef; } [NonSerialized] protected T? m_res; } public class Resource { static public Mgr mgr = new(); } public delegate T Load( string filename ); abstract class LoadHolder { public abstract object load(); } class LoadHolder : LoadHolder { public LoadHolder( Load fnLoad ) { _fnLoad = fnLoad; } public Load _fnLoad; public override object load() { return load(); } } public record class ResourceHolder( WeakReference weak, string Name, DateTime captured ) : imm.Recorded> where T : class { } //generic classes make a new static per generic type class ResCache where T : class, new() { public static T s_default = new(); public static ImmutableDictionary> s_cache = ImmutableDictionary>.Empty; } public class Mgr { static public void startup() { } static public void register( Load loader ) { Debug.Assert( !Resource.mgr.m_loaders.ContainsKey( typeof( T ) ) ); var lh = new LoadHolder( 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() { 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 lookup( string filename, string reason = "", [CallerMemberName] string dbgName = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = 0 ) where T : class, new() { return new Ref( 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( string filename ) where T : class { if( ResCache.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( filename ); return newV; } */ static public T load( string filename, string reason = "", [CallerMemberName] string dbgName = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = 0 ) where T : class, new() { if( ResCache.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( filename ); if( newV is imm.Imm imm ) { newV = (T)imm.Record( $"Loading because {reason}", dbgName, dbgPath, dbgLine ); } return newV; } static public T actualLoad( string filename ) where T : class, new() { lock(s_loadingLock) { if( s_loading.TryGetValue( filename, out var evt ) ) { if( ResCache.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; var v = loader!._fnLoad( filename ); var weak = new WeakReference( v ); var holder = new ResourceHolder( weak, $"", DateTime.Now ).Record(); log.info( $"To {typeof(T).Name} add {filename}" ); var alreadyAdded = !ImmutableInterlocked.TryAdd( ref ResCache.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.s_default; } } } //return actualLoad( filename ); } static object s_loadingLock = new object(); static ImmutableDictionary s_loading = ImmutableDictionary.Empty; //static ImmDefLoad s_deferredLoad = ImmDefLoad.Empty; public Mgr() { log.info( $"Creating Res.Mgr" ); /* var ts = new ThreadStart( deferredLoader ); m_deferredLoader = new Thread( ts ); */ LogGC.RegisterObjectId( s_loadingLock ); //m_deferredLoader.Start(); } /* void deferredLoader() { while( true ) { Thread.Sleep( 1 ); if( ImmutableInterlocked.TryDequeue( ref s_deferredLoad, out var v ) ) { v.Item2.load(); } } } //*/ ImmutableDictionary m_loaders = ImmutableDictionary.Empty; //Thread m_deferredLoader; }