diff --git a/data/JsonData.cs b/data/JsonData.cs index 0c5f696..445a4b8 100644 --- a/data/JsonData.cs +++ b/data/JsonData.cs @@ -1,16 +1,12 @@ using System; using System.IO; -using System.Xml; +using System.Text.Json; +using System.Text.Json.Serialization; using System.Reflection; -using System.Text; - - - +using System.Text.Encodings.Web; static public class json { - - static public T load( string filename ) where T : lib.JsonData { return (T)lib.JsonData.load( filename ); @@ -18,31 +14,47 @@ static public class json static public lib.JsonData load( string filename ) { - return lib.ConfigBase.load( filename, null ); + return lib.JsonData.load( filename ); } - - } namespace lib { - + // Merged the Interface and Class logic to remove ambiguity public interface JsonData { + #region Configuration + + // Centralized JSON Options for consistency across Load/Save + private static readonly JsonSerializerOptions _options = new() + { + WriteIndented = true, // Pretty print + PropertyNameCaseInsensitive = true, // Forgiving load + IncludeFields = true, // Serialize public fields (game dev standard) + ReferenceHandler = ReferenceHandler.Preserve, // Handles circular refs and polymorphism $id metadata + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, // Cleaner files + AllowTrailingCommas = true, + ReadCommentHandling = JsonCommentHandling.Skip, + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + }; + + #endregion #region SaveLoad + // Entry point for Generic Load static public T res_load( string filename ) where T : JsonData - where T : class { - return JsonData.load( filename ); + return (T)actual_load( filename, typeof( T ) ); } + // Entry point for untyped Load static public JsonData load( string filename ) { return actual_load( filename, null ); } + // Wrapper for generic load static public T load( string filename ) where T : JsonData { return (T)actual_load( filename, typeof( T ) ); @@ -50,147 +62,138 @@ namespace lib static public JsonData actual_load( string filename, Type t ) { - JsonData cfg = null; + JsonData json = null; try { - if( !File.Exists( filename ) ) + // 1. Path Sanitization + if( !File.Exists( filename ) && filename.StartsWith( "res://" ) ) { - if( filename.StartsWith( "res://" ) ) - filename.Replace( "res://", "" ); + filename = filename.Replace( "res://", "" ); } - + // 2. Load or Create if( File.Exists( filename ) ) { - FileStream fs = new FileStream( filename, FileMode.Open, FileAccess.Read ); + string jsonContent = File.ReadAllText( filename ); - XmlFormatter2 formatter = new XmlFormatter2(); + // If T is null, we can't deserialize effectively without Type info. + // Defaulting to JsonData or the passed type. + Type targetType = t ?? typeof( JsonData ); - cfg = (JsonData)( t != null ? formatter.DeserializeKnownType( fs, t ) : formatter.Deserialize( fs ) ); + json = (JsonData)JsonSerializer.Deserialize( jsonContent, targetType, _options ); - cfg.SetFilename( filename ); + if( json != null ) + { + json.Filename = filename; + } } else { - cfg = CreateTemplate( filename, t ); + json = CreateTemplate( filename, t ); } } - catch( IOException ) + catch( Exception ex ) when( ex is IOException || ex is JsonException ) { - cfg = CreateTemplate( filename, t ); + log.error( $"Failed to load {filename}: {ex.Message}" ); + json = CreateTemplate( filename, t ); } - return cfg; + return json; } private static JsonData CreateTemplate( string filename, Type t ) { log.debug( $"JsonData file {filename} not found, creating template." ); - Type[] types = new Type[0]; - object[] parms = new object[0]; + JsonData json = null; - //types[ 0 ] = typeof( string ); - //parms[ 0 ] = filename; - JsonData cfg = null; - - ConstructorInfo cons = t?.GetConstructor( types ); + // Handle potential null type if called generically without constraints + if( t == null ) + { + log.error( "Cannot create template for unknown type." ); + return null; + } try { - cfg = (JsonData)cons?.Invoke( parms ); + json = refl.CreateObject( t ) as JsonData; } catch( Exception e ) { - log.error( $"Exception while creating config {t.ToString()}, Msg {e.Message}" ); + log.error( $"Exception while creating config {t}, Msg {e.Message}" ); } - //cfg.SetFilename( filename ); - - if( ConfigCfg.s_cfg.writeOutTemplateFiles ) + // Write out the template if configured to do so + if( json != null && ConfigCfg.s_cfg.writeOutTemplateFiles ) { var templateFile = $"templates/{filename}"; - var dirName = Path.GetDirectoryName( templateFile ); Util.checkAndAddDirectory( dirName ); log.info( $"Writing out template config of type {t?.Name} in {templateFile}" ); - saveDebug( cfg, templateFile ); + // Force set filename for the template save, then restore? + // Or just pass path to saveDebug. + json.Filename = templateFile; + json.save(); } - return cfg; + return json; } - static public void saveDebug( JsonData cfg ) + static public void actual_save( JsonData json, string filename ) { - JsonData.actual_save( cfg, cfg.Filename ); - } + try + { + // Ensure directory exists + string dir = Path.GetDirectoryName( filename ); + if( !string.IsNullOrEmpty( dir ) && !Directory.Exists( dir ) ) + { + Directory.CreateDirectory( dir ); + } - static public void actual_save( JsonData cfg, String filename ) - { - FileStream fs = new( filename, FileMode.Create, FileAccess.Write ); - fs.Close(); + // Serialize to string first (safer than open stream + fail) + string jsonString = JsonSerializer.Serialize( json, json.GetType(), _options ); + File.WriteAllText( filename, jsonString ); + } + catch( Exception ex ) + { + log.exception( ex, $"Type { json.GetType().Name } {filename}: {ex.Message}" ); + } } #endregion + #region Instance Logic - public String Filename { get; } - } + //private string _filename = "{unknown}"; + public string Filename { get; set; } - /* UNUSED - public record class ConfigRec : ConfigBase - { - - } - */ - - public class JsonData : ConfigBase - { - - //private int _test = 0; - - private static ser.XmlCfg s_templateCfg = new() + static public void startup() { - Structure = ser.Datastructure.Tree, - POD = ser.POD.Elements, - }; - - - internal static ConfigCfg s_cfg = new(); - - public static ConfigCfg Cfg => s_cfg; - - static public void startup( string filename ) - { - res.Mgr.Register( ConfigBase.load ); + // Assuming res.Mgr handles the delegates correctly + //res.Mgr.Register( JsonData.load ); res.Mgr.RegisterSub( typeof( ConfigBase ) ); - - s_cfg = ConfigBase.load( filename ); - } + #endregion + } + public class JsonDataRes : JsonData, res.Resource + { + public string Filename { get; set; } + } - public JsonData() + static public class JsonDataEx + { + + static public void save( this JsonData json ) { + // Allow overriding path for templates, otherwise use object's filename + JsonData.actual_save( json, json.Filename ); } - public JsonData( string filename ) - { - _filename = filename; - } - - private string _filename = "{unknown}"; - - - public String Filename { get { return _filename; } } - - internal void SetFilename( String filename ) { _filename = filename; } - - } -} +} diff --git a/imm/Imm.cs b/imm/Imm.cs index d602eb1..9e3ae28 100644 --- a/imm/Imm.cs +++ b/imm/Imm.cs @@ -29,7 +29,7 @@ public static class imm ref T obj, Func fn, string reason = "", - [CallerMemberName] string dbgName = "", + [CallerMemberName] string dbgMethod = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = 0, [CallerArgumentExpression( "fn" )] string dbgExpression = "" ) @@ -37,7 +37,7 @@ public static class imm { // This will call the 'Timed' override if T is Timed, // or the 'Recorded' override if T is Recorded. - obj = obj.Process( fn, reason, dbgName, dbgPath, dbgLine, dbgExpression ); + obj = obj.Process( fn, reason, dbgMethod, dbgPath, dbgLine, dbgExpression ); return obj; } diff --git a/imm/io.cs b/imm/io.cs index 9cc2300..7b7f835 100644 --- a/imm/io.cs +++ b/imm/io.cs @@ -31,7 +31,7 @@ public interface Obj /// Obj Record( string reason = "Recorded", - [CallerMemberName] string dbgName = "", + [CallerMemberName] string dbgMethod = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = 0 ); } @@ -61,7 +61,7 @@ public interface Obj : Obj where T : Obj T Process( Func fn, string reason = "Processed", - [CallerMemberName] string dbgName = "", + [CallerMemberName] string dbgMethod = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = 0, [CallerArgumentExpression( "fn" )] string expStr = "" ); @@ -73,7 +73,7 @@ public interface Obj : Obj where T : Obj /// new T Record( string reason = "Recorded", - [CallerMemberName] string dbgName = "", + [CallerMemberName] string dbgMethod = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = 0 ); } @@ -190,7 +190,7 @@ public record class Versioned : Obj where T : Versioned public virtual T Process( Func fn, string reason = "Processed", - [CallerMemberName] string dbgName = "", + [CallerMemberName] string dbgMethod = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = 0, [CallerArgumentExpression( "fn" )] string expStr = "" ) @@ -215,18 +215,18 @@ public record class Versioned : Obj where T : Versioned /// public virtual T Record( string reason = "Recorded", - [CallerMemberName] string dbgName = "", + [CallerMemberName] string dbgMethod = "", [CallerFilePath] string dbgPath = "", - [CallerLineNumber] int dbgLine = 0 ) => Process( t => t, reason, dbgName, dbgPath, dbgLine ); + [CallerLineNumber] int dbgLine = 0 ) => Process( t => t, reason, dbgMethod, dbgPath, dbgLine ); /// /// Implements Obj.Record by calling the virtual T Record. /// Obj Obj.Record( string reason = "Recorded", - [CallerMemberName] string dbgName = "", + [CallerMemberName] string dbgMethod = "", [CallerFilePath] string dbgPath = "", - [CallerLineNumber] int dbgLine = 0 ) => this.Record( reason, dbgName, dbgPath, dbgLine ); + [CallerLineNumber] int dbgLine = 0 ) => this.Record( reason, dbgMethod, dbgPath, dbgLine ); } @@ -250,7 +250,7 @@ public record class Recorded : Versioned where T : Recorded public override T Process( Func fn, string reason = "", - [CallerMemberName] string dbgName = "", + [CallerMemberName] string dbgMethod = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = 0, [CallerArgumentExpression( "fn" )] string expStr = "" ) @@ -265,7 +265,7 @@ public record class Recorded : Versioned where T : Recorded { Version = current.Meta.Version + 1, Reason = reason, - MemberName = dbgName, + MemberName = dbgMethod, FilePath = dbgPath, LineNumber = dbgLine, Expression = expStr, @@ -279,11 +279,11 @@ public record class Recorded : Versioned where T : Recorded public new T Record( string reason = "Recorded", - [CallerMemberName] string dbgName = "", + [CallerMemberName] string dbgMethod = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = 0 ) { - return Process( t => t, reason, dbgName, dbgPath, dbgLine ); + return Process( t => t, reason, dbgMethod, dbgPath, dbgLine ); } } @@ -351,11 +351,11 @@ public record class Timed : Recorded where T : Timed public new T Record( string reason = "Recorded", - [CallerMemberName] string dbgName = "", + [CallerMemberName] string dbgMethod = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = 0 ) { - return Process( t => t, reason, dbgName, dbgPath, dbgLine ); + return Process( t => t, reason, dbgMethod, dbgPath, dbgLine ); } } @@ -366,13 +366,13 @@ public static class TimedExt ref T obj, Func fn, string reason = "", - [CallerMemberName] string dbgName = "", + [CallerMemberName] string dbgMethod = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = 0, [CallerArgumentExpression( "fn" )] string dbgExpression = "" ) where T : io.Timed { - obj = obj.Process( fn, reason, dbgName, dbgPath, dbgLine, dbgExpression ); + obj = obj.Process( fn, reason, dbgMethod, dbgPath, dbgLine, dbgExpression ); return obj; } } diff --git a/logging/Log.cs b/logging/Log.cs index aea1db5..9612641 100644 --- a/logging/Log.cs +++ b/logging/Log.cs @@ -625,34 +625,56 @@ static public class log logBase( $"{header}{dbgExpObj}[{type.Name}] ->", level, dbgPath, dbgLine, dbgMethod, cat, dbgExpObj ); // 6. Handle Complex Objects (Properties) - if( maxDepth == 0 ) + if( maxDepth < 0 ) { return; } + List members = new(); + refl.GetAllMembers( type, members ); + // Get all readable properties - var props = type.GetProperties( BindingFlags.Public | BindingFlags.Instance ) - .Where( p => p.CanRead && p.GetIndexParameters().Length == 0 ); + //var props = type.GetProperties( BindingFlags.Public | BindingFlags.Private | BindingFlags.Instance ) + // .Where( p => p.CanRead && p.GetIndexParameters().Length == 0 ); + + var props = members.Where( ( mi) => true ); var nextHeader = $"{header}{header}"; //bool firstProp = true; - foreach( var pi in props ) + foreach( var mi in members ) { - var name = pi.Name; - var piType = pi.PropertyType; + var is_pi = mi is PropertyInfo pi; + var is_fi = mi is FieldInfo fi; + + var name = mi.Name; + var piType = mi.MemberType; try { - var value = pi.GetValue( obj ); + object? value = GetValue( obj, mi ); props_r( value, nextHeader, --maxDepth, level, cat, prefix, dbgPath, dbgLine, dbgMethod, name ); } catch( Exception ex ) { - logBase( $"{nextHeader}{name}[{piType.Name}] ex {ex.Message}", level, dbgPath, dbgLine, dbgMethod, cat, name ); + logBase( $"{nextHeader}{name}[{mi.Name}] ex {ex.Message}", level, dbgPath, dbgLine, dbgMethod, cat, name ); } } } + static object GetValue( object obj, MemberInfo mi ) + { + if( mi is PropertyInfo pi ) + { + return pi.GetValue( obj ); + } + if( mi is FieldInfo fi ) + { + return fi.GetValue( obj ); + } + + return null; + + } //This might seem a little odd, but the intent is that usually you wont need to set notExpectedValue. static public void expected( T value, string falseString, string trueString = "", T? notExpectedValue = default( T ) ) diff --git a/reflect/refl.cs b/reflect/refl.cs index 8d93899..64a7dba 100644 --- a/reflect/refl.cs +++ b/reflect/refl.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Reflection; +using System.Runtime.Serialization; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -18,6 +19,28 @@ using System.Threading.Tasks; static public class refl { + static public T Create() + { + var t = typeof( T ); + return (T)CreateObject( t ); + } + + static public object CreateObject( Type type ) + { + object newObj = null; + if( type.GetConstructor( Type.EmptyTypes ) != null ) + { + newObj = Activator.CreateInstance( type ); + } + else + { + newObj = FormatterServices.GetUninitializedObject( type ); + } + + return newObj; + } + + public class PredEnumerator { public static PredEnumerator Create( IEnumerator en, Predicate pred ) @@ -142,6 +165,83 @@ static public class refl } + public static void GetAllMembers( Type t, List list ) + { + GetAllMembersRecursive( t, true, list ); + } + + public static void GetAllMembersRecursive( Type t, bool recurse, List list ) + { + var MemberArr = t.GetMembers( + BindingFlags.DeclaredOnly | + BindingFlags.NonPublic | + BindingFlags.Public | + BindingFlags.Instance ); + + var en = PredEnumerator.Create( MemberArr.AsEnumerable(), fa => fa.GetCustomAttribute( typeof( NonSerializedAttribute ) ) == null ); + + list.AddRange( new PredEnumerable( en ) ); + + if( recurse && t.BaseType != null && t.BaseType != typeof( object ) ) + { + GetAllMembers( t.BaseType, list ); + } + } + + public static void GetAllMembersUntil( Type t, Type tooFar, List list ) + { + var MemberArr = t.GetMembers( + BindingFlags.DeclaredOnly | + BindingFlags.NonPublic | + BindingFlags.Public | + BindingFlags.Instance ); + + var en = PredEnumerator.Create( MemberArr.AsEnumerable(), fa => fa.GetCustomAttribute( typeof( NonSerializedAttribute ) ) == null ); + + list.AddRange( new PredEnumerable( en ) ); + + if( t.BaseType != null && t.BaseType != tooFar ) + { + GetAllMembers( t.BaseType, list ); + } + } + + public static ImmutableList GetAllMembers() + { + return GetAllMembers( typeof( T ) ); + } + + public static ImmutableList GetAllMembers( Type t ) + { + { + if( s_MemberCache.TryGetValue( t, out var first ) ) + return first; + } + + //LogGC.RegisterObjectId( t ); + + lock( t ) + { + if( s_MemberCache.TryGetValue( t, out var second ) ) + return second; + + var list = new List(); + + GetAllMembers( t, list ); + + var immList = list.ToImmutableList(); + + Interlocked.Exchange( ref s_MemberCache, s_MemberCache.Add( t, immList ) ); + + return immList; + } + } + + + + + + public static void GetAllFields( Type t, List list ) { GetAllFieldsRecursive( t, true, list ); @@ -164,6 +264,7 @@ static public class refl GetAllFields( t.BaseType, list ); } } + public static void GetAllFieldsUntil( Type t, Type tooFar, List list ) { var fieldArr = t.GetFields( @@ -187,8 +288,6 @@ static public class refl return GetAllFields( typeof( T ) ); } - - public static ImmutableList GetAllFields( Type t ) { { @@ -253,6 +352,7 @@ static public class refl } + static ImmutableDictionary> s_MemberCache = ImmutableDictionary>.Empty; static ImmutableDictionary> s_fieldCache = ImmutableDictionary>.Empty; static ImmutableDictionary> s_propCache = ImmutableDictionary>.Empty; diff --git a/res/Resource.cs b/res/Resource.cs index 5f6b234..44c1043 100644 --- a/res/Resource.cs +++ b/res/Resource.cs @@ -7,11 +7,17 @@ using System.Collections.Immutable; using System.Runtime.CompilerServices; using System.Threading; using System.Linq; +using System.Text.Json.Serialization; #nullable enable namespace res; +public interface Resource +{ + public string Filename { get; set; } +} + // A delegate representing a function that can load a resource of type T. public delegate T Load( string filename ); @@ -33,13 +39,13 @@ public abstract class Ref protected Ref( string filename, string reason = "", - [CallerMemberName] string dbgName = "", + [CallerMemberName] string dbgMethod = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = 0 ) { Filename = filename; Reason = reason; - DbgName = dbgName; + DbgName = dbgMethod; DbgPath = dbgPath; DbgLine = dbgLine; @@ -53,7 +59,7 @@ public abstract class Ref /// The loaded resource object. public abstract object Lookup( string reason = "", - [CallerMemberName] string dbgName = "", + [CallerMemberName] string dbgMethod = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = 0 ); @@ -66,6 +72,41 @@ public abstract class Ref /// Internal method to trigger the initial load (used by deferred loading, if implemented). /// internal virtual void InternalLoad() { } + + static public res.Ref Create( T res, [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = -1, [CallerMemberName] string dbgMethod = "" ) where T : class, Resource, new() + { + if( res.Filename == null ) + { + res.Filename = $"{{{typeof( T ).Name}}}.{Ref.s_codeRes}"; + log.debug( $"Code Res for {typeof( T ).Name} named {res.Filename}" ); + } + + return Ref.CreateAsset( res, res.Filename ); + } + + static public res.Ref Create( string filename, T res, [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = -1, [CallerMemberName] string dbgMethod = "" ) where T : class, new() + { + if( string.IsNullOrWhiteSpace( filename ) ) + { + filename = $"{{{typeof( T ).Name}}}.{Ref.s_codeRes}"; + log.debug( $"Code Res for {typeof( T ).Name} named {filename}" ); + } + + return Ref.CreateAsset( res, filename ); + } + + static public res.Ref Create( string filename, [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = -1, [CallerMemberName] string dbgMethod = "" ) where T : class, new() + { + if( string.IsNullOrWhiteSpace( filename ) ) + { + filename = $"{{{typeof( T ).Name}}}.{Ref.s_codeRes}"; + log.debug( $"Code Res for {typeof( T ).Name} named {filename}" ); + } + + var res = refl.Create(); + + return Ref.CreateAsset( res, filename ); + } } /// @@ -76,6 +117,9 @@ public abstract class Ref [DebuggerDisplay( "Path = {Filename} / Res = {m_res}" )] public class Ref : Ref where T : class, new() { + static internal int s_codeRes = 1024; + + [JsonIgnore] [NonSerialized] private T? m_res; @@ -88,17 +132,18 @@ public class Ref : Ref where T : class, new() /// Gets the resource, loading it if necessary. /// //[Deprecated("Use Res property instead.")] + [JsonIgnore] public T res => m_res ?? Lookup(); public Ref( string filename = "", string reason = "", - [CallerMemberName] string dbgName = "", + [CallerMemberName] string dbgMethod = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = 0 ) : base( - !string.IsNullOrWhiteSpace( filename ) ? filename : $"{{{dbgName}_{Path.GetFileNameWithoutExtension( dbgPath )}}}", - reason, dbgName, dbgPath, dbgLine ) + !string.IsNullOrWhiteSpace( filename ) ? filename : $"{{{dbgMethod}_{Path.GetFileNameWithoutExtension( dbgPath )}}}", + reason, dbgMethod, dbgPath, dbgLine ) { if( VerboseLogging ) log.info( $"Ref Created: {GetType().Name}<{typeof( T ).Name}> {Filename}" ); @@ -110,7 +155,7 @@ public class Ref : Ref where T : class, new() /// public override T Lookup( string reason = "", - [CallerMemberName] string dbgName = "", + [CallerMemberName] string dbgMethod = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = 0 ) { @@ -119,7 +164,7 @@ public class Ref : Ref where T : class, new() return m_res; // Load using the Mgr. - m_res = Mgr.Load( Filename, $"Ref lookup (orig: {Reason}) bcs {reason}", dbgName, dbgPath, dbgLine ); + m_res = Mgr.Load( Filename, $"Ref lookup (orig: {Reason}) bcs {reason}", dbgMethod, dbgPath, dbgLine ); if( VerboseLogging ) log.info( $"Ref.Lookup: {GetType().Name}<{typeof( T ).Name}> {Filename}" ); return m_res; @@ -131,11 +176,11 @@ public class Ref : Ref where T : class, new() /// public override object Lookup( string reason = "", - [CallerMemberName] string dbgName = "", + [CallerMemberName] string dbgMethod = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = 0) { - return Lookup(reason, dbgName, dbgPath, dbgLine); + return Lookup(reason, dbgMethod, dbgPath, dbgLine); } */ @@ -159,7 +204,7 @@ public class Ref : Ref where T : class, new() public static Ref CreateAsset( T value, string path, string reason = "", - [CallerMemberName] string dbgName = "", + [CallerMemberName] string dbgMethod = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = 0 ) { @@ -186,7 +231,7 @@ public class Ref : Ref where T : class, new() var createReason = $"CreateAsset: {value.GetType().Name} - {immMeta?.Reason ?? "N/A"}"; - var newRef = new Ref( path, $"{createReason} bcs {reason}", dbgName, dbgPath, dbgLine ); + var newRef = new Ref( path, $"{createReason} bcs {reason}", dbgMethod, dbgPath, dbgLine ); // We should make the newRef hold the 'value' immediately, // or ensure loading it back gives the same 'value'. @@ -209,6 +254,9 @@ internal record ResourceHolder( WeakReference WeakRef, string Name, DateTi /// public static class Mgr { + + + // Internal holder for type-specific loaders. private abstract class LoadHolder { @@ -216,7 +264,7 @@ public static class Mgr // Its not yet working, and maybe shouldnt exist public abstract object Load( string filename, string reason = "", - [CallerMemberName] string dbgName = "", + [CallerMemberName] string dbgMethod = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = 0 ); @@ -227,7 +275,7 @@ public static class Mgr private readonly Load _fnLoad; public LoadHolder( Load fnLoad ) { _fnLoad = fnLoad; } public override object Load( string filename, string reason = "", - [CallerMemberName] string dbgName = "", + [CallerMemberName] string dbgMethod = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = 0 ) => _fnLoad( filename )!; @@ -335,27 +383,26 @@ public static class Mgr } } - /// /// Creates a Ref for a given filename. /// public static Ref Lookup( string filename, string reason = "", - [CallerMemberName] string dbgName = "", + [CallerMemberName] string dbgMethod = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = 0 ) where T : class, new() { - return new Ref( filename, reason, dbgName, dbgPath, dbgLine ); + return new Ref( filename, reason, dbgMethod, dbgPath, dbgLine ); } /// /// Loads a resource, handling caching and thread-safe loading. /// - public static T Load( + internal static T Load( string filename, string reason = "", - [CallerMemberName] string dbgName = "", + [CallerMemberName] string dbgMethod = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = 0 ) where T : class, new() { @@ -376,8 +423,8 @@ public static class Mgr } // 4. Perform the actual load - log.warn( $"Loading {typeof( T ).Name}: {filename} ({reason} at {dbgName}:{dbgLine})" ); - var newValue = ActualLoad( filename, reason, dbgName, dbgPath, dbgLine ); + log.warn( $"Loading {typeof( T ).Name}: {filename} ({reason} at {dbgMethod}:{dbgLine})" ); + var newValue = ActualLoad( filename, reason, dbgMethod, dbgPath, dbgLine ); // 5. Cache the new value CacheResource( filename, newValue, reason ); @@ -397,7 +444,6 @@ public static class Mgr log.info( $"Cached {typeof( T ).Name}: {filename} ({reason})" ); } - /// /// Tries to retrieve a resource from the cache. /// @@ -422,14 +468,14 @@ public static class Mgr private static T ActualLoad( string filename, string reason = "", - [CallerMemberName] string dbgName = "", + [CallerMemberName] string dbgMethod = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = 0 ) where T : class, new() { if( s_loaders.TryGetValue( typeof( T ), out var loaderHolder ) ) { - var loadedObject = loaderHolder.Load( filename, reason, dbgName, dbgPath, dbgLine ); + var loadedObject = loaderHolder.Load( filename, reason, dbgMethod, dbgPath, dbgLine ); if( loadedObject is T value ) { var meta = ( value as io.Obj )?.Meta; @@ -437,7 +483,7 @@ public static class Mgr // If it's an immutable object, record its loading. if( value is io.Obj imm ) { - return (T)imm.Record( $"Loading bcs {reason}", dbgName, dbgPath, dbgLine ); + return (T)imm.Record( $"Loading bcs {reason}", dbgMethod, dbgPath, dbgLine ); } return value; } diff --git a/ser/XmlSer_Core.cs b/ser/XmlSer_Core.cs index 2ec98c3..ed791f7 100644 --- a/ser/XmlSer_Core.cs +++ b/ser/XmlSer_Core.cs @@ -121,7 +121,9 @@ public partial class ObjectHandler : ITypeHandler } - private (object? obj, long id) GetOrCreateInstance( XmlSer xml, XmlElement elem, Type type, object? existing ) + + + static public (object? obj, long id) GetOrCreateInstance( XmlSer xml, XmlElement elem, Type type, object? existing ) { long id = -1; bool first = true; @@ -137,14 +139,7 @@ public partial class ObjectHandler : ITypeHandler object? newObj = null; try { - if( type.GetConstructor( Type.EmptyTypes ) != null ) - { - newObj = Activator.CreateInstance( type ); - } - else - { - newObj = FormatterServices.GetUninitializedObject( type ); - } + newObj = refl.CreateObject( type ); } catch( Exception ex ) {