Little things

This commit is contained in:
Marc Hernandez 2026-02-14 02:06:00 -08:00
parent 81ba16a0d1
commit 4d9cda5f48
7 changed files with 321 additions and 155 deletions

View File

@ -1,16 +1,12 @@
using System; using System;
using System.IO; using System.IO;
using System.Xml; using System.Text.Json;
using System.Text.Json.Serialization;
using System.Reflection; using System.Reflection;
using System.Text; using System.Text.Encodings.Web;
static public class json static public class json
{ {
static public T load<T>( string filename ) where T : lib.JsonData static public T load<T>( string filename ) where T : lib.JsonData
{ {
return (T)lib.JsonData.load<T>( filename ); return (T)lib.JsonData.load<T>( filename );
@ -18,31 +14,47 @@ static public class json
static public lib.JsonData load( string filename ) static public lib.JsonData load( string filename )
{ {
return lib.ConfigBase.load( filename, null ); return lib.JsonData.load( filename );
} }
} }
namespace lib namespace lib
{ {
// Merged the Interface and Class logic to remove ambiguity
public interface JsonData 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 #region SaveLoad
// Entry point for Generic Load
static public T res_load<T>( string filename ) where T : JsonData static public T res_load<T>( string filename ) where T : JsonData
where T : class
{ {
return JsonData.load<T>( filename ); return (T)actual_load( filename, typeof( T ) );
} }
// Entry point for untyped Load
static public JsonData load( string filename ) static public JsonData load( string filename )
{ {
return actual_load( filename, null ); return actual_load( filename, null );
} }
// Wrapper for generic load
static public T load<T>( string filename ) where T : JsonData static public T load<T>( string filename ) where T : JsonData
{ {
return (T)actual_load( filename, typeof( T ) ); return (T)actual_load( filename, typeof( T ) );
@ -50,147 +62,138 @@ namespace lib
static public JsonData actual_load( string filename, Type t ) static public JsonData actual_load( string filename, Type t )
{ {
JsonData cfg = null; JsonData json = null;
try try
{ {
if( !File.Exists( filename ) ) // 1. Path Sanitization
if( !File.Exists( filename ) && filename.StartsWith( "res://" ) )
{ {
if( filename.StartsWith( "res://" ) ) filename = filename.Replace( "res://", "" );
filename.Replace( "res://", "" );
} }
// 2. Load or Create
if( File.Exists( filename ) ) 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 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 ) private static JsonData CreateTemplate( string filename, Type t )
{ {
log.debug( $"JsonData file {filename} not found, creating template." ); log.debug( $"JsonData file {filename} not found, creating template." );
Type[] types = new Type[0]; JsonData json = null;
object[] parms = new object[0];
//types[ 0 ] = typeof( string ); // Handle potential null type if called generically without constraints
//parms[ 0 ] = filename; if( t == null )
JsonData cfg = null; {
log.error( "Cannot create template for unknown type." );
ConstructorInfo cons = t?.GetConstructor( types ); return null;
}
try try
{ {
cfg = (JsonData)cons?.Invoke( parms ); json = refl.CreateObject( t ) as JsonData;
} }
catch( Exception e ) 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 ); // Write out the template if configured to do so
if( json != null && ConfigCfg.s_cfg.writeOutTemplateFiles )
if( ConfigCfg.s_cfg.writeOutTemplateFiles )
{ {
var templateFile = $"templates/{filename}"; var templateFile = $"templates/{filename}";
var dirName = Path.GetDirectoryName( templateFile ); var dirName = Path.GetDirectoryName( templateFile );
Util.checkAndAddDirectory( dirName ); Util.checkAndAddDirectory( dirName );
log.info( $"Writing out template config of type {t?.Name} in {templateFile}" ); 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 ) // Serialize to string first (safer than open stream + fail)
{ string jsonString = JsonSerializer.Serialize( json, json.GetType(), _options );
FileStream fs = new( filename, FileMode.Create, FileAccess.Write ); File.WriteAllText( filename, jsonString );
fs.Close(); }
catch( Exception ex )
{
log.exception( ex, $"Type { json.GetType().Name } {filename}: {ex.Message}" );
}
} }
#endregion #endregion
#region Instance Logic
public String Filename { get; } //private string _filename = "{unknown}";
} public string Filename { get; set; }
/* UNUSED static public void startup()
public record class ConfigRec : ConfigBase
{
}
*/
public class JsonData : ConfigBase
{
//private int _test = 0;
private static ser.XmlCfg s_templateCfg = new()
{ {
Structure = ser.Datastructure.Tree, // Assuming res.Mgr handles the delegates correctly
POD = ser.POD.Elements, //res.Mgr.Register( JsonData.load );
};
internal static ConfigCfg s_cfg = new();
public static ConfigCfg Cfg => s_cfg;
static public void startup( string filename )
{
res.Mgr.Register( ConfigBase.load );
res.Mgr.RegisterSub( typeof( ConfigBase ) ); res.Mgr.RegisterSub( typeof( ConfigBase ) );
s_cfg = ConfigBase.load<ConfigCfg>( 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; }
} }
}
}

View File

@ -29,7 +29,7 @@ public static class imm
ref T obj, ref T obj,
Func<T, T> fn, Func<T, T> fn,
string reason = "", string reason = "",
[CallerMemberName] string dbgName = "", [CallerMemberName] string dbgMethod = "",
[CallerFilePath] string dbgPath = "", [CallerFilePath] string dbgPath = "",
[CallerLineNumber] int dbgLine = 0, [CallerLineNumber] int dbgLine = 0,
[CallerArgumentExpression( "fn" )] string dbgExpression = "" ) [CallerArgumentExpression( "fn" )] string dbgExpression = "" )
@ -37,7 +37,7 @@ public static class imm
{ {
// This will call the 'Timed' override if T is Timed, // This will call the 'Timed' override if T is Timed,
// or the 'Recorded' override if T is Recorded. // 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; return obj;
} }

View File

@ -31,7 +31,7 @@ public interface Obj
/// </summary> /// </summary>
Obj Record( Obj Record(
string reason = "Recorded", string reason = "Recorded",
[CallerMemberName] string dbgName = "", [CallerMemberName] string dbgMethod = "",
[CallerFilePath] string dbgPath = "", [CallerFilePath] string dbgPath = "",
[CallerLineNumber] int dbgLine = 0 ); [CallerLineNumber] int dbgLine = 0 );
} }
@ -61,7 +61,7 @@ public interface Obj<T> : Obj where T : Obj<T>
T Process( T Process(
Func<T, T> fn, Func<T, T> fn,
string reason = "Processed", string reason = "Processed",
[CallerMemberName] string dbgName = "", [CallerMemberName] string dbgMethod = "",
[CallerFilePath] string dbgPath = "", [CallerFilePath] string dbgPath = "",
[CallerLineNumber] int dbgLine = 0, [CallerLineNumber] int dbgLine = 0,
[CallerArgumentExpression( "fn" )] string expStr = "" ); [CallerArgumentExpression( "fn" )] string expStr = "" );
@ -73,7 +73,7 @@ public interface Obj<T> : Obj where T : Obj<T>
/// </summary> /// </summary>
new T Record( new T Record(
string reason = "Recorded", string reason = "Recorded",
[CallerMemberName] string dbgName = "", [CallerMemberName] string dbgMethod = "",
[CallerFilePath] string dbgPath = "", [CallerFilePath] string dbgPath = "",
[CallerLineNumber] int dbgLine = 0 ); [CallerLineNumber] int dbgLine = 0 );
} }
@ -190,7 +190,7 @@ public record class Versioned<T> : Obj<T> where T : Versioned<T>
public virtual T Process( public virtual T Process(
Func<T, T> fn, Func<T, T> fn,
string reason = "Processed", string reason = "Processed",
[CallerMemberName] string dbgName = "", [CallerMemberName] string dbgMethod = "",
[CallerFilePath] string dbgPath = "", [CallerFilePath] string dbgPath = "",
[CallerLineNumber] int dbgLine = 0, [CallerLineNumber] int dbgLine = 0,
[CallerArgumentExpression( "fn" )] string expStr = "" ) [CallerArgumentExpression( "fn" )] string expStr = "" )
@ -215,18 +215,18 @@ public record class Versioned<T> : Obj<T> where T : Versioned<T>
/// </summary> /// </summary>
public virtual T Record( public virtual T Record(
string reason = "Recorded", string reason = "Recorded",
[CallerMemberName] string dbgName = "", [CallerMemberName] string dbgMethod = "",
[CallerFilePath] string dbgPath = "", [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 );
/// <summary> /// <summary>
/// Implements Obj.Record by calling the virtual T Record. /// Implements Obj.Record by calling the virtual T Record.
/// </summary> /// </summary>
Obj Obj.Record( Obj Obj.Record(
string reason = "Recorded", string reason = "Recorded",
[CallerMemberName] string dbgName = "", [CallerMemberName] string dbgMethod = "",
[CallerFilePath] string dbgPath = "", [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<T> : Versioned<T> where T : Recorded<T>
public override T Process( public override T Process(
Func<T, T> fn, Func<T, T> fn,
string reason = "", string reason = "",
[CallerMemberName] string dbgName = "", [CallerMemberName] string dbgMethod = "",
[CallerFilePath] string dbgPath = "", [CallerFilePath] string dbgPath = "",
[CallerLineNumber] int dbgLine = 0, [CallerLineNumber] int dbgLine = 0,
[CallerArgumentExpression( "fn" )] string expStr = "" ) [CallerArgumentExpression( "fn" )] string expStr = "" )
@ -265,7 +265,7 @@ public record class Recorded<T> : Versioned<T> where T : Recorded<T>
{ {
Version = current.Meta.Version + 1, Version = current.Meta.Version + 1,
Reason = reason, Reason = reason,
MemberName = dbgName, MemberName = dbgMethod,
FilePath = dbgPath, FilePath = dbgPath,
LineNumber = dbgLine, LineNumber = dbgLine,
Expression = expStr, Expression = expStr,
@ -279,11 +279,11 @@ public record class Recorded<T> : Versioned<T> where T : Recorded<T>
public new T Record( public new T Record(
string reason = "Recorded", string reason = "Recorded",
[CallerMemberName] string dbgName = "", [CallerMemberName] string dbgMethod = "",
[CallerFilePath] string dbgPath = "", [CallerFilePath] string dbgPath = "",
[CallerLineNumber] int dbgLine = 0 ) [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<T> : Recorded<T> where T : Timed<T>
public new T Record( public new T Record(
string reason = "Recorded", string reason = "Recorded",
[CallerMemberName] string dbgName = "", [CallerMemberName] string dbgMethod = "",
[CallerFilePath] string dbgPath = "", [CallerFilePath] string dbgPath = "",
[CallerLineNumber] int dbgLine = 0 ) [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, ref T obj,
Func<T, T> fn, Func<T, T> fn,
string reason = "", string reason = "",
[CallerMemberName] string dbgName = "", [CallerMemberName] string dbgMethod = "",
[CallerFilePath] string dbgPath = "", [CallerFilePath] string dbgPath = "",
[CallerLineNumber] int dbgLine = 0, [CallerLineNumber] int dbgLine = 0,
[CallerArgumentExpression( "fn" )] string dbgExpression = "" [CallerArgumentExpression( "fn" )] string dbgExpression = ""
) where T : io.Timed<T> ) where T : io.Timed<T>
{ {
obj = obj.Process( fn, reason, dbgName, dbgPath, dbgLine, dbgExpression ); obj = obj.Process( fn, reason, dbgMethod, dbgPath, dbgLine, dbgExpression );
return obj; return obj;
} }
} }

View File

@ -625,34 +625,56 @@ static public class log
logBase( $"{header}{dbgExpObj}[{type.Name}] ->", level, dbgPath, dbgLine, dbgMethod, cat, dbgExpObj ); logBase( $"{header}{dbgExpObj}[{type.Name}] ->", level, dbgPath, dbgLine, dbgMethod, cat, dbgExpObj );
// 6. Handle Complex Objects (Properties) // 6. Handle Complex Objects (Properties)
if( maxDepth == 0 ) if( maxDepth < 0 )
{ {
return; return;
} }
List<MemberInfo> members = new();
refl.GetAllMembers( type, members );
// Get all readable properties // Get all readable properties
var props = type.GetProperties( BindingFlags.Public | BindingFlags.Instance ) //var props = type.GetProperties( BindingFlags.Public | BindingFlags.Private | BindingFlags.Instance )
.Where( p => p.CanRead && p.GetIndexParameters().Length == 0 ); // .Where( p => p.CanRead && p.GetIndexParameters().Length == 0 );
var props = members.Where( ( mi) => true );
var nextHeader = $"{header}{header}"; var nextHeader = $"{header}{header}";
//bool firstProp = true; //bool firstProp = true;
foreach( var pi in props ) foreach( var mi in members )
{ {
var name = pi.Name; var is_pi = mi is PropertyInfo pi;
var piType = pi.PropertyType; var is_fi = mi is FieldInfo fi;
var name = mi.Name;
var piType = mi.MemberType;
try try
{ {
var value = pi.GetValue( obj ); object? value = GetValue( obj, mi );
props_r( value, nextHeader, --maxDepth, level, cat, prefix, dbgPath, dbgLine, dbgMethod, name ); props_r( value, nextHeader, --maxDepth, level, cat, prefix, dbgPath, dbgLine, dbgMethod, name );
} }
catch( Exception ex ) 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. //This might seem a little odd, but the intent is that usually you wont need to set notExpectedValue.
static public void expected<T>( T value, string falseString, string trueString = "", T? notExpectedValue = default( T ) ) static public void expected<T>( T value, string falseString, string trueString = "", T? notExpectedValue = default( T ) )

View File

@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Runtime.Serialization;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -18,6 +19,28 @@ using System.Threading.Tasks;
static public class refl static public class refl
{ {
static public T Create<T>()
{
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 class PredEnumerator
{ {
public static PredEnumerator<T> Create<T>( IEnumerator<T> en, Predicate<T> pred ) public static PredEnumerator<T> Create<T>( IEnumerator<T> en, Predicate<T> pred )
@ -142,6 +165,83 @@ static public class refl
} }
public static void GetAllMembers( Type t, List<MemberInfo> list )
{
GetAllMembersRecursive( t, true, list );
}
public static void GetAllMembersRecursive( Type t, bool recurse, List<MemberInfo> list )
{
var MemberArr = t.GetMembers(
BindingFlags.DeclaredOnly |
BindingFlags.NonPublic |
BindingFlags.Public |
BindingFlags.Instance );
var en = PredEnumerator.Create<MemberInfo>( MemberArr.AsEnumerable<MemberInfo>(), fa => fa.GetCustomAttribute( typeof( NonSerializedAttribute ) ) == null );
list.AddRange( new PredEnumerable<MemberInfo>( en ) );
if( recurse && t.BaseType != null && t.BaseType != typeof( object ) )
{
GetAllMembers( t.BaseType, list );
}
}
public static void GetAllMembersUntil( Type t, Type tooFar, List<MemberInfo> list )
{
var MemberArr = t.GetMembers(
BindingFlags.DeclaredOnly |
BindingFlags.NonPublic |
BindingFlags.Public |
BindingFlags.Instance );
var en = PredEnumerator.Create<MemberInfo>( MemberArr.AsEnumerable<MemberInfo>(), fa => fa.GetCustomAttribute( typeof( NonSerializedAttribute ) ) == null );
list.AddRange( new PredEnumerable<MemberInfo>( en ) );
if( t.BaseType != null && t.BaseType != tooFar )
{
GetAllMembers( t.BaseType, list );
}
}
public static ImmutableList<MemberInfo> GetAllMembers<T>()
{
return GetAllMembers( typeof( T ) );
}
public static ImmutableList<MemberInfo> 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<MemberInfo>();
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<FieldInfo> list ) public static void GetAllFields( Type t, List<FieldInfo> list )
{ {
GetAllFieldsRecursive( t, true, list ); GetAllFieldsRecursive( t, true, list );
@ -164,6 +264,7 @@ static public class refl
GetAllFields( t.BaseType, list ); GetAllFields( t.BaseType, list );
} }
} }
public static void GetAllFieldsUntil( Type t, Type tooFar, List<FieldInfo> list ) public static void GetAllFieldsUntil( Type t, Type tooFar, List<FieldInfo> list )
{ {
var fieldArr = t.GetFields( var fieldArr = t.GetFields(
@ -187,8 +288,6 @@ static public class refl
return GetAllFields( typeof( T ) ); return GetAllFields( typeof( T ) );
} }
public static ImmutableList<FieldInfo> GetAllFields( Type t ) public static ImmutableList<FieldInfo> GetAllFields( Type t )
{ {
{ {
@ -253,6 +352,7 @@ static public class refl
} }
static ImmutableDictionary<Type, ImmutableList<MemberInfo>> s_MemberCache = ImmutableDictionary<Type, ImmutableList<MemberInfo>>.Empty;
static ImmutableDictionary<Type, ImmutableList<FieldInfo>> s_fieldCache = ImmutableDictionary<Type, ImmutableList<FieldInfo>>.Empty; static ImmutableDictionary<Type, ImmutableList<FieldInfo>> s_fieldCache = ImmutableDictionary<Type, ImmutableList<FieldInfo>>.Empty;
static ImmutableDictionary<Type, ImmutableList<PropertyInfo>> s_propCache = ImmutableDictionary<Type, ImmutableList<PropertyInfo>>.Empty; static ImmutableDictionary<Type, ImmutableList<PropertyInfo>> s_propCache = ImmutableDictionary<Type, ImmutableList<PropertyInfo>>.Empty;

View File

@ -7,11 +7,17 @@ using System.Collections.Immutable;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Threading; using System.Threading;
using System.Linq; using System.Linq;
using System.Text.Json.Serialization;
#nullable enable #nullable enable
namespace res; namespace res;
public interface Resource
{
public string Filename { get; set; }
}
// A delegate representing a function that can load a resource of type T. // A delegate representing a function that can load a resource of type T.
public delegate T Load<out T>( string filename ); public delegate T Load<out T>( string filename );
@ -33,13 +39,13 @@ public abstract class Ref
protected Ref( protected Ref(
string filename, string filename,
string reason = "", string reason = "",
[CallerMemberName] string dbgName = "", [CallerMemberName] string dbgMethod = "",
[CallerFilePath] string dbgPath = "", [CallerFilePath] string dbgPath = "",
[CallerLineNumber] int dbgLine = 0 ) [CallerLineNumber] int dbgLine = 0 )
{ {
Filename = filename; Filename = filename;
Reason = reason; Reason = reason;
DbgName = dbgName; DbgName = dbgMethod;
DbgPath = dbgPath; DbgPath = dbgPath;
DbgLine = dbgLine; DbgLine = dbgLine;
@ -53,7 +59,7 @@ public abstract class Ref
/// <returns>The loaded resource object.</returns> /// <returns>The loaded resource object.</returns>
public abstract object Lookup( public abstract object Lookup(
string reason = "", string reason = "",
[CallerMemberName] string dbgName = "", [CallerMemberName] string dbgMethod = "",
[CallerFilePath] string dbgPath = "", [CallerFilePath] string dbgPath = "",
[CallerLineNumber] int dbgLine = 0 ); [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 method to trigger the initial load (used by deferred loading, if implemented).
/// </summary> /// </summary>
internal virtual void InternalLoad() { } internal virtual void InternalLoad() { }
static public res.Ref<T> Create<T>( 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<T>.s_codeRes}";
log.debug( $"Code Res for {typeof( T ).Name} named {res.Filename}" );
}
return Ref<T>.CreateAsset( res, res.Filename );
}
static public res.Ref<T> Create<T>( 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<T>.s_codeRes}";
log.debug( $"Code Res for {typeof( T ).Name} named {filename}" );
}
return Ref<T>.CreateAsset( res, filename );
}
static public res.Ref<T> Create<T>( 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<T>.s_codeRes}";
log.debug( $"Code Res for {typeof( T ).Name} named {filename}" );
}
var res = refl.Create<T>();
return Ref<T>.CreateAsset( res, filename );
}
} }
/// <summary> /// <summary>
@ -76,6 +117,9 @@ public abstract class Ref
[DebuggerDisplay( "Path = {Filename} / Res = {m_res}" )] [DebuggerDisplay( "Path = {Filename} / Res = {m_res}" )]
public class Ref<T> : Ref where T : class, new() public class Ref<T> : Ref where T : class, new()
{ {
static internal int s_codeRes = 1024;
[JsonIgnore]
[NonSerialized] [NonSerialized]
private T? m_res; private T? m_res;
@ -88,17 +132,18 @@ public class Ref<T> : Ref where T : class, new()
/// Gets the resource, loading it if necessary. /// Gets the resource, loading it if necessary.
/// </summary> /// </summary>
//[Deprecated("Use Res property instead.")] //[Deprecated("Use Res property instead.")]
[JsonIgnore]
public T res => m_res ?? Lookup(); public T res => m_res ?? Lookup();
public Ref( public Ref(
string filename = "", string filename = "",
string reason = "", string reason = "",
[CallerMemberName] string dbgName = "", [CallerMemberName] string dbgMethod = "",
[CallerFilePath] string dbgPath = "", [CallerFilePath] string dbgPath = "",
[CallerLineNumber] int dbgLine = 0 ) [CallerLineNumber] int dbgLine = 0 )
: base( : base(
!string.IsNullOrWhiteSpace( filename ) ? filename : $"{{{dbgName}_{Path.GetFileNameWithoutExtension( dbgPath )}}}", !string.IsNullOrWhiteSpace( filename ) ? filename : $"{{{dbgMethod}_{Path.GetFileNameWithoutExtension( dbgPath )}}}",
reason, dbgName, dbgPath, dbgLine ) reason, dbgMethod, dbgPath, dbgLine )
{ {
if( VerboseLogging ) if( VerboseLogging )
log.info( $"Ref<T> Created: {GetType().Name}<{typeof( T ).Name}> {Filename}" ); log.info( $"Ref<T> Created: {GetType().Name}<{typeof( T ).Name}> {Filename}" );
@ -110,7 +155,7 @@ public class Ref<T> : Ref where T : class, new()
/// </summary> /// </summary>
public override T Lookup( public override T Lookup(
string reason = "", string reason = "",
[CallerMemberName] string dbgName = "", [CallerMemberName] string dbgMethod = "",
[CallerFilePath] string dbgPath = "", [CallerFilePath] string dbgPath = "",
[CallerLineNumber] int dbgLine = 0 ) [CallerLineNumber] int dbgLine = 0 )
{ {
@ -119,7 +164,7 @@ public class Ref<T> : Ref where T : class, new()
return m_res; return m_res;
// Load using the Mgr. // Load using the Mgr.
m_res = Mgr.Load<T>( Filename, $"Ref lookup (orig: {Reason}) bcs {reason}", dbgName, dbgPath, dbgLine ); m_res = Mgr.Load<T>( Filename, $"Ref lookup (orig: {Reason}) bcs {reason}", dbgMethod, dbgPath, dbgLine );
if( VerboseLogging ) if( VerboseLogging )
log.info( $"Ref.Lookup: {GetType().Name}<{typeof( T ).Name}> {Filename}" ); log.info( $"Ref.Lookup: {GetType().Name}<{typeof( T ).Name}> {Filename}" );
return m_res; return m_res;
@ -131,11 +176,11 @@ public class Ref<T> : Ref where T : class, new()
/// </summary> /// </summary>
public override object Lookup( public override object Lookup(
string reason = "", string reason = "",
[CallerMemberName] string dbgName = "", [CallerMemberName] string dbgMethod = "",
[CallerFilePath] string dbgPath = "", [CallerFilePath] string dbgPath = "",
[CallerLineNumber] int dbgLine = 0) [CallerLineNumber] int dbgLine = 0)
{ {
return Lookup(reason, dbgName, dbgPath, dbgLine); return Lookup(reason, dbgMethod, dbgPath, dbgLine);
} }
*/ */
@ -159,7 +204,7 @@ public class Ref<T> : Ref where T : class, new()
public static Ref<T> CreateAsset( public static Ref<T> CreateAsset(
T value, string path, T value, string path,
string reason = "", string reason = "",
[CallerMemberName] string dbgName = "", [CallerMemberName] string dbgMethod = "",
[CallerFilePath] string dbgPath = "", [CallerFilePath] string dbgPath = "",
[CallerLineNumber] int dbgLine = 0 ) [CallerLineNumber] int dbgLine = 0 )
{ {
@ -186,7 +231,7 @@ public class Ref<T> : Ref where T : class, new()
var createReason = $"CreateAsset: {value.GetType().Name} - {immMeta?.Reason ?? "N/A"}"; var createReason = $"CreateAsset: {value.GetType().Name} - {immMeta?.Reason ?? "N/A"}";
var newRef = new Ref<T>( path, $"{createReason} bcs {reason}", dbgName, dbgPath, dbgLine ); var newRef = new Ref<T>( path, $"{createReason} bcs {reason}", dbgMethod, dbgPath, dbgLine );
// We should make the newRef hold the 'value' immediately, // We should make the newRef hold the 'value' immediately,
// or ensure loading it back gives the same 'value'. // or ensure loading it back gives the same 'value'.
@ -209,6 +254,9 @@ internal record ResourceHolder<T>( WeakReference<T> WeakRef, string Name, DateTi
/// </summary> /// </summary>
public static class Mgr public static class Mgr
{ {
// Internal holder for type-specific loaders. // Internal holder for type-specific loaders.
private abstract class LoadHolder private abstract class LoadHolder
{ {
@ -216,7 +264,7 @@ public static class Mgr
// Its not yet working, and maybe shouldnt exist // Its not yet working, and maybe shouldnt exist
public abstract object Load( string filename, public abstract object Load( string filename,
string reason = "", string reason = "",
[CallerMemberName] string dbgName = "", [CallerMemberName] string dbgMethod = "",
[CallerFilePath] string dbgPath = "", [CallerFilePath] string dbgPath = "",
[CallerLineNumber] int dbgLine = 0 [CallerLineNumber] int dbgLine = 0
); );
@ -227,7 +275,7 @@ public static class Mgr
private readonly Load<T> _fnLoad; private readonly Load<T> _fnLoad;
public LoadHolder( Load<T> fnLoad ) { _fnLoad = fnLoad; } public LoadHolder( Load<T> fnLoad ) { _fnLoad = fnLoad; }
public override object Load( string filename, string reason = "", public override object Load( string filename, string reason = "",
[CallerMemberName] string dbgName = "", [CallerMemberName] string dbgMethod = "",
[CallerFilePath] string dbgPath = "", [CallerFilePath] string dbgPath = "",
[CallerLineNumber] int dbgLine = 0 [CallerLineNumber] int dbgLine = 0
) => _fnLoad( filename )!; ) => _fnLoad( filename )!;
@ -335,27 +383,26 @@ public static class Mgr
} }
} }
/// <summary> /// <summary>
/// Creates a Ref<T> for a given filename. /// Creates a Ref<T> for a given filename.
/// </summary> /// </summary>
public static Ref<T> Lookup<T>( public static Ref<T> Lookup<T>(
string filename, string filename,
string reason = "", string reason = "",
[CallerMemberName] string dbgName = "", [CallerMemberName] string dbgMethod = "",
[CallerFilePath] string dbgPath = "", [CallerFilePath] string dbgPath = "",
[CallerLineNumber] int dbgLine = 0 ) where T : class, new() [CallerLineNumber] int dbgLine = 0 ) where T : class, new()
{ {
return new Ref<T>( filename, reason, dbgName, dbgPath, dbgLine ); return new Ref<T>( filename, reason, dbgMethod, dbgPath, dbgLine );
} }
/// <summary> /// <summary>
/// Loads a resource, handling caching and thread-safe loading. /// Loads a resource, handling caching and thread-safe loading.
/// </summary> /// </summary>
public static T Load<T>( internal static T Load<T>(
string filename, string filename,
string reason = "", string reason = "",
[CallerMemberName] string dbgName = "", [CallerMemberName] string dbgMethod = "",
[CallerFilePath] string dbgPath = "", [CallerFilePath] string dbgPath = "",
[CallerLineNumber] int dbgLine = 0 ) where T : class, new() [CallerLineNumber] int dbgLine = 0 ) where T : class, new()
{ {
@ -376,8 +423,8 @@ public static class Mgr
} }
// 4. Perform the actual load // 4. Perform the actual load
log.warn( $"Loading {typeof( T ).Name}: {filename} ({reason} at {dbgName}:{dbgLine})" ); log.warn( $"Loading {typeof( T ).Name}: {filename} ({reason} at {dbgMethod}:{dbgLine})" );
var newValue = ActualLoad<T>( filename, reason, dbgName, dbgPath, dbgLine ); var newValue = ActualLoad<T>( filename, reason, dbgMethod, dbgPath, dbgLine );
// 5. Cache the new value // 5. Cache the new value
CacheResource( filename, newValue, reason ); CacheResource( filename, newValue, reason );
@ -397,7 +444,6 @@ public static class Mgr
log.info( $"Cached {typeof( T ).Name}: {filename} ({reason})" ); log.info( $"Cached {typeof( T ).Name}: {filename} ({reason})" );
} }
/// <summary> /// <summary>
/// Tries to retrieve a resource from the cache. /// Tries to retrieve a resource from the cache.
/// </summary> /// </summary>
@ -422,14 +468,14 @@ public static class Mgr
private static T ActualLoad<T>( private static T ActualLoad<T>(
string filename, string filename,
string reason = "", string reason = "",
[CallerMemberName] string dbgName = "", [CallerMemberName] string dbgMethod = "",
[CallerFilePath] string dbgPath = "", [CallerFilePath] string dbgPath = "",
[CallerLineNumber] int dbgLine = 0 [CallerLineNumber] int dbgLine = 0
) where T : class, new() ) where T : class, new()
{ {
if( s_loaders.TryGetValue( typeof( T ), out var loaderHolder ) ) 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 ) if( loadedObject is T value )
{ {
var meta = ( value as io.Obj )?.Meta; 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 it's an immutable object, record its loading.
if( value is io.Obj imm ) 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; return value;
} }

View File

@ -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; long id = -1;
bool first = true; bool first = true;
@ -137,14 +139,7 @@ public partial class ObjectHandler : ITypeHandler
object? newObj = null; object? newObj = null;
try try
{ {
if( type.GetConstructor( Type.EmptyTypes ) != null ) newObj = refl.CreateObject( type );
{
newObj = Activator.CreateInstance( type );
}
else
{
newObj = FormatterServices.GetUninitializedObject( type );
}
} }
catch( Exception ex ) catch( Exception ex )
{ {