diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..dddec09 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,93 @@ + +# SharpLib and general coding guidelines + +## 1. CORE PHILOSOPHY + +* **Leaky Abstractions:** All abstractions are always leaky. Do not hide complexity. Expose failure modes, costs, and "why" parameters. +* **Visual Cost:** "Expensive things must look expensive." No hidden RPCs. Use explicit Message structs and `Send` methods. +* **Fast & Introspectable:** Code must be highly performant (hot-path aware) but deeply debuggable (`log`, `DebugOld`, `reason` strings). + +## 2. NAMING & SEMANTICS + +* **Leaf Libraries:** Core libs (`io`, `imm`, `net`, `log`) are 2-5 chars. **NEVER** use `using` for them. Always type the prefix (e.g., `imm.Process`). +* **No Redundancy:** + * **No "Get":** `Player()` not `GetPlayer()`. + * **No Echo:** `Send(msg)` not `SendMsg(msg)`. + * **No "I" Prefix:** Interfaces are `Renderer`, not `IRenderer`. +* **Architecture:** Slow/Pausable Realtime. Entity Component System (Pub/Sub). + +## 3. LOGGING (`log` static class) + +* **Constraint:** **NO** `Console.WriteLine`, `Debug.WriteLine`, or `ILogger`. +* **Mechanism:** The logging system is very fast. It only queues the log on the main thread. It collects the caller debug info, and uses the directory name of the file for a default category (this can be overridden) +* **Functional Tracing:** + * `var x = log.info( $"Got {log.var(Calc())} from the calculation" );` // This both logs the info and prints what happened +* **Standard:** `log.info`, `log.debug`, `log.warn`, `log.error`. +* **Finer grained:** `log.trace`, `log.high` +* Use log.exception( ex, $"[what was trying to be done]" ); +* **Introspection:** `log.logProps(obj, "Header")` and `log.exception(ex, "Context")`. +* put important info as far to the left as possible, even at the cost of poor wording + +## 4. IMMUTABLE STATE (`io`, `imm`) + +* **Rule:** Objects inheriting `Versioned`, `Recorded`, `Timed` are **IMMUTABLE**. +* **Change Pipeline:** MUST use `Process`. + * **Ref Helper (Preferred):** `imm.Process(ref _state, s => s with { X = 1 }, "Reason");` + * **Instance:** `_state = _state.Process(s => s with { X = 1 }, "Reason");` +* **The "Hole Punch":** Always propagate a `string reason` parameter in your methods to feed the `Process` log. +* **History:** `obj.DebugOld` is for **DEBUG ONLY**. Do not base game logic on it. + +## 5. Visual Cost + +* Unless something is a function call, it should look like a function call. For example RPCs are bad, sending a message is good. +* **No RPCs:** Do not make network calls look like functions. +* **Pattern:** Construct Struct -> Send. + * *Bad:* `proxy.Move(x,y)` + * *Good:* `Net.Send(new MoveMsg(x,y))` + +## FEW-SHOT EXAMPLES (Strictly Imitate) + +```csharp +// 1. NAMING & LOGGING +// Explicit 'log' usage, no 'Get', no 'Console' +public void Init() +{ + log.startup( "log/current_project.log" ); + + var waitTime = 1.0f; + + log.info( $"Waiting for {waitTime}" ); + + try + { + obj.SomeOperation(); + } + catch( Exception ex ) + { + log.exception( ex, $"" ); + } + + // 2. IMMUTABLE PROCESS + // Reason "Init" passed down. Ref pattern used. + imm.Process(ref _state, s => s with { Ready = true }, "Init"); +} + +// 3. LOGIC FLOW +public void Update(float dt) +{ + // 'Player()' not 'GetPlayer()' + var p = Player(); + + // Visual Cost: Explicit allocation of message struct + if (p.Active) + { + Net.Send(new HeartbeatMsg(dt)); + } +} + +// 5. INTROSPECTION API +// "Punching a hole" with the reason parameter +public void Equip(Item i, string reason = "Equip") +{ + imm.Process( ref inv, s => s.Add(i), reason); +} diff --git a/BADAGENTSBAD.BAD b/BADAGENTSBAD.BAD new file mode 100644 index 0000000..d37049b --- /dev/null +++ b/BADAGENTSBAD.BAD @@ -0,0 +1,103 @@ +# SharpLib - Core Library Reference + +SharpLib is a comprehensive C# utility library providing essential functionality for .NET applications. It offers a wide range of utilities, mathematical operations, and helper classes to streamline development. + +## Core Components + +### Math & Geometry +- **Vector2/3/4**: Vector mathematics with extensive operations +- **Matrix**: 4x4 matrix operations for transformations +- **Quaternion**: Quaternion math for 3D rotations +- **BoundingBox/BoundingSphere**: Collision detection primitives +- **Ray/Plane**: Geometric raycasting and plane operations +- **Color**: Color manipulation with multiple formats (RGBA, HSV, etc.) +- **MathUtil**: Mathematical helper functions and constants +- **Angle**: Angle handling utilities + +### Data Structures & Collections +- **Immutable collections**: Immutable List, FSM (Finite State Machine), Versioned, Recorded, Timed objects +- **Pod**: Plain Old Data structures +- **LinqExtensions**: Extended LINQ functionality +- **RandomEx**: Enhanced random number generation +- **Guid**: GUID utilities + +### Utilities & Helpers +- **StringEx**: String manipulation extensions +- **Debug**: Debugging utilities +- **Exec**: Process execution helpers +- **Utilities**: General utility functions +- **Token**: Token handling utilities +- **Id**: ID generation utilities + +### Reflection & Metadata +- **Reflection utilities**: Runtime type inspection and manipulation +- **Attributes**: Custom attributes for data handling + +### System Integration +- **Logging**: Logging infrastructure with structured logging, source location tracking, and multiple log levels +- **Time/Timer/Clock**: Time management utilities +- **Resources**: Resource management helpers with lazy loading and caching +- **Interop**: Platform interop capabilities +- **Config**: Configuration management with XML serialization and template creation +- **Immutable**: Immutable collections with versioning (Versioned), recording (Recorded), and timing (Timed) capabilities + +## Key Features + +- **Cross-platform compatibility**: Targets .NET 9.0 and 10.0 +- **Unsafe code support**: For performance-critical operations +- **Extensive documentation**: Well-documented APIs with examples +- **Type safety**: Strong typing throughout the library +- **Performance focused**: Optimized for speed and memory usage + +## Usage Examples + +```csharp +// Vector math +var vector = new Vector3(1, 2, 3); +var normalized = vector.Normalized(); + +// Color manipulation +var color = Color.FromRgb(255, 128, 0); +var hsv = color.ToHsv(); + +// Bitwise operations +if (bit.On(flags, MyEnum.Flag1)) +{ + // Handle flag +} + +// Random generation +var random = new RandomEx(); +var value = random.NextDouble(); + +// Logging with source location tracking +log.Info("Application started", cat: "app"); +log.Var("User ID", userId); +log.Operations(() => { + // Some operation +}); + +// Configuration loading +var config = cfg.load("config.xml"); + +// Resource management +var texture = res.Mgr.Lookup("assets/texture.png"); + +// Immutable collections +var list = new List(new[] { 1, 2, 3 }); +var newList = list.Add(4); +``` + +## Target Frameworks + +- .NET 9.0 +- .NET 10.0 + +## Dependencies + +- Microsoft.CodeAnalysis.CSharp +- Optional (for functional programming patterns) +- System.Collections.Immutable +- System.ValueTuple +- Microsoft.Diagnostics.NETCore.Client +- Microsoft.Diagnostics.Tracing.TraceEvent diff --git a/SharpLib.csproj b/SharpLib.csproj index d28658b..85d9064 100644 --- a/SharpLib.csproj +++ b/SharpLib.csproj @@ -11,9 +11,12 @@ true true 14.0 - Copyright 2003..2025 Marc Hernandez - A base set of functionality en + disable + disable + false + false + true {data.FileName}" ); - } - - private void OnMethodDetails( MethodLoadUnloadVerboseTraceData data ) - { - if( FilterOutEvent( data ) ) - return; - - // care only about jitted methods - if( !data.IsJitted ) - return; - - var method = GetProcessMethods( data.ProcessID ) - .Add( data.MethodStartAddress, data.MethodSize, data.MethodNamespace, data.MethodName, data.MethodSignature ); - - log.info( $"0x{data.MethodStartAddress.ToString( "x12" )} - {data.MethodSize,6} | {data.MethodName}" ); - } - - private MethodStore GetProcessMethods( int pid ) - { - if( !_processes.Methods.TryGetValue( pid, out var methods ) ) - { - methods = new MethodStore( pid ); - _processes.Methods[pid] = methods; - } - return methods; - } - - - private void OnSampleObjectAllocation( GCSampledObjectAllocationTraceData data ) - { - if( FilterOutEvent( data ) ) - return; - - var typeName = GetProcessTypeName( data.ProcessID, data.TypeID ); - if( data.TotalSizeForTypeSample >= 85000 ) - { - var message = $"{data.ProcessID}.{data.ThreadID} - {data.TimeStampRelativeMSec,12} | Alloc {GetProcessTypeName( data.ProcessID, data.TypeID )} ({data.TotalSizeForTypeSample})"; - log.info( message ); - } - GetProcessAllocations( data.ProcessID ) - .AddAllocation( - data.ThreadID, - (ulong)data.TotalSizeForTypeSample, - (ulong)data.ObjectCountForTypeSample, - typeName - ); - } - - private ProcessAllocations GetProcessAllocations( int pid ) - { - if( !_processes.Allocations.TryGetValue( pid, out var allocations ) ) - { - allocations = new ProcessAllocations( pid ); - _processes.Allocations[pid] = allocations; - } - return allocations; - } - - private void OnClrStackWalk( ClrStackWalkTraceData data ) - { - if( FilterOutEvent( data ) ) - return; - - //var message = $"{data.ProcessID}.{data.ThreadID} - {data.TimeStampRelativeMSec,12} | {data.FrameCount} frames"; - //log.info(message); - - var callstack = BuildCallStack( data ); - GetProcessAllocations( data.ProcessID ).AddStack( data.ThreadID, callstack ); - //DumpStack(data); - } - - private AddressStack BuildCallStack( ClrStackWalkTraceData data ) - { - var length = data.FrameCount; - AddressStack stack = new AddressStack( length ); - - // frame 0 is the last frame of the stack (i.e. last called method) - for( int i = 0; i < length; i++ ) - { - stack.AddFrame( data.InstructionPointer( i ) ); - } - - return stack; - } - - private void DumpStack( ClrStackWalkTraceData data ) - { - var methods = GetProcessMethods( data.ProcessID ); - for( int i = 0; i < data.FrameCount; i++ ) - { - var address = data.InstructionPointer( i ); - log.info( methods.GetFullName( address ) ); - } - log.info( $"" ); - } - - private void OnTypeBulkType( GCBulkTypeTraceData data ) - { - if( FilterOutEvent( data ) ) - return; - - ProcessTypeMapping mapping = GetProcessTypesMapping( data.ProcessID ); - for( int currentType = 0; currentType < data.Count; currentType++ ) - { - GCBulkTypeValues value = data.Values( currentType ); - mapping[value.TypeID] = value.TypeName; - } - } - - private ProcessTypeMapping GetProcessTypesMapping( int pid ) - { - ProcessTypeMapping mapping; - if( !_processes.Types.TryGetValue( pid, out mapping ) ) - { - AssociateProcess( pid ); - - mapping = new ProcessTypeMapping( pid ); - _processes.Types[pid] = mapping; - } - return mapping; - } - - private void AssociateProcess( int pid ) - { - try - { - _processes.Names[pid] = Process.GetProcessById( pid ).ProcessName; - } - catch( Exception ) - { - log.info( $"? {pid}" ); - // we might not have access to the process - } - } - - private string GetProcessTypeName( int pid, ulong typeID ) - { - if( !_processes.Types.TryGetValue( pid, out var mapping ) ) - { - return typeID.ToString(); - } - - var name = mapping[typeID]; - return string.IsNullOrEmpty( name ) ? typeID.ToString() : name; - } - - private bool FilterOutEvent( TraceEvent data ) - { - return ( data.ProcessID == _currentPid ); - } - } -} diff --git a/prof/MemoryPipe.cs b/prof/MemoryPipe.cs deleted file mode 100644 index 01314d7..0000000 --- a/prof/MemoryPipe.cs +++ /dev/null @@ -1,393 +0,0 @@ -using Microsoft.Diagnostics.NETCore.Client; -using Microsoft.Diagnostics.Tracing; -using Microsoft.Diagnostics.Tracing.Etlx; -using Microsoft.Diagnostics.Tracing.Parsers; -using Microsoft.Diagnostics.Tracing.Parsers.Clr; -using Microsoft.Diagnostics.Tracing.Parsers.Kernel; -using Microsoft.Diagnostics.Tracing.Session; -using ProfilerHelpers; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Diagnostics.Tracing; -using System.Threading; -using System.Threading.Tasks; -using TraceKeywords = Microsoft.Diagnostics.Tracing.Parsers.ClrTraceEventParser.Keywords; - -namespace Tracing -{ - public class MemoryPipe - { - private readonly DiagnosticsClient _client; - private readonly PerProcessProfilingState _processes; - - // because we are not interested in self monitoring - private readonly int _currentPid; - - private int _started = 0; - - Thread _thread; - - public MemoryPipe() - { - - _currentPid = Process.GetCurrentProcess().Id; - - _client = new DiagnosticsClient( _currentPid ); - - _processes = new(); - - - var threadStart = new ThreadStart( () => Start( true ) ); - _thread = new Thread( threadStart ); - _thread.Start(); - - - } - - //public async Task StartAsync( bool allAllocations ) - public void Start( bool allAllocations ) - { - if( Interlocked.CompareExchange( ref _started, 1, 0 ) == 1 ) - { - throw new InvalidOperationException( "Impossible to start profiling more than once." ); - } - - log.info( $"Starting MemoryPipe on thread {Thread.CurrentThread.ManagedThreadId}" ); - - var providers = new List() - { - new EventPipeProvider("Microsoft-Windows-DotNETRuntime", - EventLevel.Verbose, (long)( - TraceKeywords.GC | - TraceKeywords.Contention | - TraceKeywords.Debugger | - TraceKeywords.Exception | - TraceKeywords.GCAllObjectAllocation | - TraceKeywords.GCSampledObjectAllocationHigh | - TraceKeywords.GCSampledObjectAllocationLow | - TraceKeywords.Security | - TraceKeywords.Threading | - TraceKeywords.Type | - TraceKeywords.TypeDiagnostic | - TraceKeywords.WaitHandle | - TraceKeywords.All - ) ) - }; - - - //await Task.Factory.StartNew( () => { - using EventPipeSession session = _client.StartEventPipeSession( providers, false ); - - //log.info( $"SetupProviders" ); - //SetupProviders( session, allAllocations ); - var source = new EventPipeEventSource( session.EventStream ); - - - log.info( $"SetupListeners" ); - SetupListeners( source ); - - log.info( $"Run Process" ); - source.Process(); - - log.info( $"Done" ); - //} ); - } - - private void SetupProviders( TraceEventSession session, bool noSampling ) - { - // Note: the kernel provider MUST be the first provider to be enabled - // If the kernel provider is not enabled, the callstacks for CLR events are still received - // but the symbols are not found (except for the application itself) - // Maybe a TraceEvent implementation details triggered when a module (image) is loaded - var success = true; - - //* - log.info( $"EnableKernelProvider" ); - success = log.var( session.EnableKernelProvider( KernelTraceEventParser.Keywords.None | - // KernelTraceEventParser.Keywords.ImageLoad | - // KernelTraceEventParser.Keywords.Process - 0, - KernelTraceEventParser.Keywords.None - ) ); - log.info( $"EnableKernelProvider {success}" ); - //*/ - - // The CLR source code indicates that the provider must be set before the monitored application starts - // Note: no real difference between High and Low - ClrTraceEventParser.Keywords eventsKeyword = noSampling - ? ClrTraceEventParser.Keywords.GCSampledObjectAllocationLow | ClrTraceEventParser.Keywords.GCSampledObjectAllocationHigh - : ClrTraceEventParser.Keywords.GCSampledObjectAllocationLow - ; - - log.info( $"EnableProvider" ); - success = log.var( session.EnableProvider( - ClrTraceEventParser.ProviderGuid, - TraceEventLevel.Verbose, // this is needed in order to receive GCSampledObjectAllocation event - (ulong)( - - eventsKeyword | - - // required to receive the BulkType events that allows - // mapping between the type ID received in the allocation events - ClrTraceEventParser.Keywords.GCHeapAndTypeNames | - ClrTraceEventParser.Keywords.Type | - - // events related to JITed methods - ClrTraceEventParser.Keywords.Jit | // Turning on JIT events is necessary to resolve JIT compiled code - ClrTraceEventParser.Keywords.JittedMethodILToNativeMap | // This is needed if you want line number information in the stacks - ClrTraceEventParser.Keywords.Loader | // You must include loader events as well to resolve JIT compiled code. - - // this is mandatory to get the callstacks in each CLR event payload. - //ClrTraceEventParser.Keywords.Stack | - - 0 - ) - ) ); - log.info( $"EnableProvider {success}" ); - - - // Note: ClrRundown is not needed because only new processes will be monitored - } - - private void SetupListeners( EventPipeEventSource source ) - { - // register for high and low keyword - // if both are set, each allocation will trigger an event (beware performance issues...) - //source.Clr.GCSampledObjectAllocation += OnSampleObjectAllocation; - source.Clr.GCAllocationTick += OnAllocTick; - - // required to receive the mapping between type ID (received in GCSampledObjectAllocation) - // and their name (received in TypeBulkType) - source.Clr.TypeBulkType += OnTypeBulkType; - - // messages to get callstacks - // the correlation seems to be as "simple" as taking the last event on the same thread - source.Clr.ClrStackWalk += OnClrStackWalk; - - // needed to get JITed method details - source.Clr.MethodLoadVerbose += OnMethodDetails; - source.Clr.MethodDCStartVerboseV2 += OnMethodDetails; - - source.Clr.ContentionLockCreated += OnLockCreated; - source.Clr.ContentionStart += OnLockStart; - source.Clr.ContentionStop += OnLockStop; - - // get notified when a module is load to map the corresponding symbols - source.Kernel.ImageLoad += OnImageLoad; - } - - private void OnAllocTick( GCAllocationTickTraceData data ) - { - if( FilterOutEvent( data ) ) - return; - - //log.info( $"*** RAW: {data}" ); - - //var callStack = data.CallStack(); - //var caller = callStack.Caller; - - //log.info( $"Call stack {callStack}" ); - - var thisThreadId = Thread.CurrentThread.ManagedThreadId; - - var typeName = GetProcessTypeName( data.ProcessID, data.TypeID ); - //if( data.TotalSizeForTypeSample >= 85000 ) - { - var message = $"{data.ThreadID}/{thisThreadId} Alloc {data.ObjectSize,8} at 0x{data.Address:0000000000000000} in {data.TypeName} (or {GetProcessTypeName( data.ProcessID, data.TypeID )}) "; - log.info( message ); - } - GetProcessAllocations( data.ProcessID ) - .AddAllocation( - data.ThreadID, - (ulong)data.ObjectSize, - (ulong)1, - typeName - ); - } - - private void OnLockCreated( ContentionLockCreatedTraceData data ) - { - log.info( $"{data}" ); - } - - private void OnLockStart( ContentionStartTraceData data ) - { - log.info( $"{data}" ); - } - - private void OnLockStop( ContentionStopTraceData data ) - { - log.info( $"{data}" ); - } - - private void OnImageLoad( ImageLoadTraceData data ) - { - //if( FilterOutEvent( data ) ) - // return; - - log.info( $"{data}" ); - - //GetProcessMethods( data.ProcessID ).AddModule( data.FileName, data.ImageBase, data.ImageSize ); - - log.info( $"{data.ProcessID}.{data.ThreadID} --> {data.FileName}" ); - } - - private void OnMethodDetails( MethodLoadUnloadVerboseTraceData data ) - { - //if( FilterOutEvent( data ) ) - // return; - - //log.info( $"{data}" ); - - // care only about jitted methods - if( !data.IsJitted ) - return; - - var method = GetProcessMethods( data.ProcessID ) - .Add( data.MethodStartAddress, data.MethodSize, data.MethodNamespace, data.MethodName, data.MethodSignature ); - - //log.info( $"0x{data.MethodStartAddress.ToString( "x12" )} - {data.MethodSize,6} | {data.MethodName}" ); - } - - private MethodStore GetProcessMethods( int pid ) - { - if( !_processes.Methods.TryGetValue( pid, out var methods ) ) - { - methods = new MethodStore( pid ); - _processes.Methods[pid] = methods; - } - return methods; - } - - - private void OnSampleObjectAllocation( GCSampledObjectAllocationTraceData data ) - { - if( FilterOutEvent( data ) ) - return; - - //log.info( $"{data}" ); - - var typeName = GetProcessTypeName( data.ProcessID, data.TypeID ); - //if( data.TotalSizeForTypeSample >= 85000 ) - { - var message = $"{data.ProcessID}.{data.ThreadID} - {data.TimeStampRelativeMSec,12} | Alloc {GetProcessTypeName( data.ProcessID, data.TypeID )} ({data.TotalSizeForTypeSample})"; - log.info( message ); - } - GetProcessAllocations( data.ProcessID ) - .AddAllocation( - data.ThreadID, - (ulong)data.TotalSizeForTypeSample, - (ulong)data.ObjectCountForTypeSample, - typeName - ); - } - - private ProcessAllocations GetProcessAllocations( int pid ) - { - if( !_processes.Allocations.TryGetValue( pid, out var allocations ) ) - { - allocations = new ProcessAllocations( pid ); - _processes.Allocations[pid] = allocations; - } - return allocations; - } - - private void OnClrStackWalk( ClrStackWalkTraceData data ) - { - var message = $"{data.ProcessID}.{data.ThreadID} - {data.TimeStampRelativeMSec,12} | {data.FrameCount} frames"; - log.info( message ); - - var callstack = BuildCallStack( data ); - GetProcessAllocations( data.ProcessID ).AddStack( data.ThreadID, callstack ); - //DumpStack(data); - } - - private AddressStack BuildCallStack( ClrStackWalkTraceData data ) - { - //log.info( $"{data}" ); - - var length = data.FrameCount; - AddressStack stack = new AddressStack( length ); - - // frame 0 is the last frame of the stack (i.e. last called method) - for( int i = 0; i < length; i++ ) - { - stack.AddFrame( data.InstructionPointer( i ) ); - } - - return stack; - } - - private void DumpStack( ClrStackWalkTraceData data ) - { - if( FilterOutEvent( data ) ) - return; - - log.info( $"{data}" ); - - var methods = GetProcessMethods( data.ProcessID ); - for( int i = 0; i < data.FrameCount; i++ ) - { - var address = data.InstructionPointer( i ); - log.info( methods.GetFullName( address ) ); - } - log.info( $"" ); - } - - private void OnTypeBulkType( GCBulkTypeTraceData data ) - { - - ProcessTypeMapping mapping = GetProcessTypesMapping( data.ProcessID ); - for( int currentType = 0; currentType < data.Count; currentType++ ) - { - GCBulkTypeValues value = data.Values( currentType ); - mapping[value.TypeID] = value.TypeName; - //log.info( $"{value}" ); - } - } - - private ProcessTypeMapping GetProcessTypesMapping( int pid ) - { - ProcessTypeMapping mapping; - if( !_processes.Types.TryGetValue( pid, out mapping ) ) - { - AssociateProcess( pid ); - - mapping = new ProcessTypeMapping( pid ); - _processes.Types[pid] = mapping; - } - return mapping; - } - - private void AssociateProcess( int pid ) - { - try - { - _processes.Names[pid] = Process.GetProcessById( pid ).ProcessName; - } - catch( Exception ) - { - log.info( $"? {pid}" ); - // we might not have access to the process - } - } - - private string GetProcessTypeName( int pid, ulong typeID ) - { - if( !_processes.Types.TryGetValue( pid, out var mapping ) ) - { - return typeID.ToString(); - } - - var name = mapping[typeID]; - return string.IsNullOrEmpty( name ) ? typeID.ToString() : name; - } - - private bool FilterOutEvent( TraceEvent data ) - { - return data.ThreadID == Thread.CurrentThread.ManagedThreadId; - //return false; // ( data.ProcessID == _currentPid ); - } - } -} diff --git a/prof/MethodInfo.cs b/prof/MethodInfo.cs deleted file mode 100644 index 0facada..0000000 --- a/prof/MethodInfo.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System; - -namespace ProfilerHelpers -{ - public class MethodInfo - { - private readonly ulong _startAddress; - private readonly int _size; - private readonly string _fullName; - - internal MethodInfo( ulong startAddress, int size, string namespaceAndTypeName, string name, string signature ) - { - _startAddress = startAddress; - _size = size; - _fullName = ComputeFullName( startAddress, namespaceAndTypeName, name, signature ); - } - - private string ComputeFullName( ulong startAddress, string namespaceAndTypeName, string name, string signature ) - { - var fullName = signature; - - // constructor case: name = .ctor | namespaceAndTypeName = A.B.typeName | signature = ... (parameters) - // --> A.B.typeName(parameters) - if( name == ".ctor" ) - { - return $"{namespaceAndTypeName}{ExtractParameters( signature )}"; - } - - // general case: name = Foo | namespaceAndTypeName = A.B.typeName | signature = ... (parameters) - // --> A.B.Foo(parameters) - fullName = $"{namespaceAndTypeName}.{name}{ExtractParameters( signature )}"; - return fullName; - } - - private string ExtractTypeName( string namespaceAndTypeName ) - { - var pos = namespaceAndTypeName.LastIndexOf( ".", StringComparison.Ordinal ); - if( pos == -1 ) - { - return namespaceAndTypeName; - } - - // skip the . - pos++; - - return namespaceAndTypeName.Substring( pos ); - } - - private string ExtractParameters( string signature ) - { - var pos = signature.IndexOf( " (" ); - if( pos == -1 ) - { - return "(???)"; - } - - // skip double space - pos += 2; - - var parameters = signature.Substring( pos ); - return parameters; - } - - public ulong StartAddress => _startAddress; - public int Size => _size; - public string FullName => _fullName; - } -} diff --git a/prof/MethodStore.cs b/prof/MethodStore.cs deleted file mode 100644 index 9f09cc0..0000000 --- a/prof/MethodStore.cs +++ /dev/null @@ -1,209 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Runtime.InteropServices; -using System.Text; - -namespace ProfilerHelpers -{ - public class MethodStore : IDisposable - { - // JITed methods information (start address + size + signature) - private readonly List _methods; - - // addresses from callstacks already matching (address -> full name) - private readonly Dictionary _cache; - - // for native methods, rely on dbghelp API - // so the process handle is required - private IntPtr _hProcess; - private Process _process; - private readonly int _pid; - - public MethodStore( int pid, bool loadModules = false ) - { - // it may be possible to open the process - // in that case, _hProcess = IntPtr.Zero - _pid = pid; - - _methods = new List( 1024 ); - _cache = new Dictionary(); - - _hProcess = BindToProcess( pid, loadModules ); - } - - private IntPtr BindToProcess( int pid, bool loadModules ) - { - try - { - _process = Process.GetProcessById( pid ); - - if( !SymInitialize( _process.Handle, loadModules ) ) - return IntPtr.Zero; - - return _process.Handle; - } - catch( Exception x ) - { - Console.WriteLine( $"Error while binding pid #{pid} to DbgHelp:" ); - Console.WriteLine( x.Message ); - return IntPtr.Zero; - } - } - - private bool SymInitialize( IntPtr hProcess, bool loadModules = false ) - { - // read https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-symsetoptions for more details - // maybe SYMOPT_NO_PROMPTS and SYMOPT_FAIL_CRITICAL_ERRORS could be used - NativeDbgHelp.SymSetOptions( - NativeDbgHelp.SYMOPT_DEFERRED_LOADS | // performance optimization - NativeDbgHelp.SYMOPT_UNDNAME // C++ names are not mangled - ); - - // https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-syminitialize - // search path for symbols: - // - The current working directory of the application - // - The _NT_SYMBOL_PATH environment variable - // - The _NT_ALTERNATE_SYMBOL_PATH environment variable - // - // passing false as last parameter means that we will need to call SymLoadModule64 - // each time a module is loaded in the process - return NativeDbgHelp.SymInitialize( hProcess, null, loadModules ); - } - - public MethodInfo Add( ulong address, int size, string namespaceAndTypeName, string name, string signature ) - { - var method = new MethodInfo( address, size, namespaceAndTypeName, name, signature ); - _methods.Add( method ); - return method; - } - - public string GetFullName( ulong address ) - { - if( _cache.TryGetValue( address, out var fullName ) ) - return fullName; - - // look for managed methods - for( int i = 0; i < _methods.Count; i++ ) - { - var method = _methods[i]; - - if( ( address >= method.StartAddress ) && ( address < method.StartAddress + (ulong)method.Size ) ) - { - fullName = method.FullName; - _cache[address] = fullName; - return fullName; - } - } - - // look for native methods - fullName = GetNativeMethodName( address ); - _cache[address] = fullName; - - return fullName; - } - - private string GetNativeMethodName( ulong address ) - { - var symbol = new NativeDbgHelp.SYMBOL_INFO(); - symbol.MaxNameLen = 1024; - symbol.SizeOfStruct = (uint)Marshal.SizeOf( symbol ) - 1024; // char buffer is not counted - // the ANSI version of SymFromAddr is called so each character is 1 byte long - - if( NativeDbgHelp.SymFromAddr( _hProcess, address, out var displacement, ref symbol ) ) - { - var buffer = new StringBuilder( symbol.Name.Length ); - - // remove weird "$##" at the end of some symbols - var pos = symbol.Name.LastIndexOf( "$##" ); - if( pos == -1 ) - buffer.Append( symbol.Name ); - else - buffer.Append( symbol.Name, 0, pos ); - - // add offset if any - if( displacement != 0 ) - buffer.Append( $"+0x{displacement}" ); - - return buffer.ToString(); - } - - // default value is just the address in HEX -#if DEBUG - return ( $"0x{address:x} (SymFromAddr failed with 0x{Marshal.GetLastWin32Error():x})" ); -#else - return $"0x{address:x}"; -#endif - - } - - const int ERROR_SUCCESS = 0; - public void AddModule( string filename, ulong baseOfDll, int sizeOfDll ) - { - var baseAddress = NativeDbgHelp.SymLoadModule64( _hProcess, IntPtr.Zero, filename, null, baseOfDll, (uint)sizeOfDll ); - if( baseAddress == 0 ) - { - // should work if the same module is added more than once - if( Marshal.GetLastWin32Error() == ERROR_SUCCESS ) - return; - - Console.WriteLine( $"SymLoadModule64 failed for {filename}" ); - } - } - - public void Dispose() - { - if( _hProcess == IntPtr.Zero ) - return; - _hProcess = IntPtr.Zero; - - _process.Dispose(); - } - } - - internal static class NativeDbgHelp - { - // from C:\Program Files (x86)\Windows Kits\10\Debuggers\inc\dbghelp.h - public const uint SYMOPT_UNDNAME = 0x00000002; - public const uint SYMOPT_DEFERRED_LOADS = 0x00000004; - - [StructLayout( LayoutKind.Sequential )] - public struct SYMBOL_INFO - { - public uint SizeOfStruct; - public uint TypeIndex; // Type Index of symbol - private ulong Reserved1; - private ulong Reserved2; - public uint Index; - public uint Size; - public ulong ModBase; // Base Address of module containing this symbol - public uint Flags; - public ulong Value; // Value of symbol, ValuePresent should be 1 - public ulong Address; // Address of symbol including base address of module - public uint Register; // register holding value or pointer to value - public uint Scope; // scope of the symbol - public uint Tag; // pdb classification - public uint NameLen; // Actual length of name - public uint MaxNameLen; - [MarshalAs( UnmanagedType.ByValTStr, SizeConst = 1024 )] - public string Name; - } - - [DllImport( "dbghelp.dll", SetLastError = true )] - public static extern bool SymInitialize( IntPtr hProcess, string userSearchPath, bool invadeProcess ); - - [DllImport( "dbghelp.dll", SetLastError = true )] - public static extern uint SymSetOptions( uint symOptions ); - - [DllImport( "dbghelp.dll", SetLastError = true, CharSet = CharSet.Ansi )] - public static extern ulong SymLoadModule64( IntPtr hProcess, IntPtr hFile, string imageName, string moduleName, ulong baseOfDll, uint sizeOfDll ); - - // use ANSI version to ensure the right size of the structure - // read https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/ns-dbghelp-symbol_info - [DllImport( "dbghelp.dll", SetLastError = true, CharSet = CharSet.Ansi )] - public static extern bool SymFromAddr( IntPtr hProcess, ulong address, out ulong displacement, ref SYMBOL_INFO symbol ); - - [DllImport( "dbghelp.dll", SetLastError = true )] - public static extern bool SymCleanup( IntPtr hProcess ); - } -} diff --git a/prof/PerProcessProfilingState.cs b/prof/PerProcessProfilingState.cs deleted file mode 100644 index 8a3c2d5..0000000 --- a/prof/PerProcessProfilingState.cs +++ /dev/null @@ -1,33 +0,0 @@ -using ProfilerHelpers; -using System; -using System.Collections.Generic; - -namespace Tracing -{ - public class PerProcessProfilingState : IDisposable - { - private bool _disposed; - - private readonly Dictionary _processNames = new Dictionary(); - private readonly Dictionary _perProcessTypes = new Dictionary(); - private readonly Dictionary _perProcessAllocations = new Dictionary(); - private readonly Dictionary _methods = new Dictionary(); - - public Dictionary Names => _processNames; - public Dictionary Types => _perProcessTypes; - public Dictionary Allocations => _perProcessAllocations; - public Dictionary Methods => _methods; - - public void Dispose() - { - if( _disposed ) - return; - _disposed = true; - - foreach( var methodStore in _methods.Values ) - { - methodStore.Dispose(); - } - } - } -} diff --git a/prof/ProcessAllocations.cs b/prof/ProcessAllocations.cs deleted file mode 100644 index acdc4dd..0000000 --- a/prof/ProcessAllocations.cs +++ /dev/null @@ -1,131 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Tracing -{ - - public class ProcessAllocations - { - private readonly int _pid; - private readonly Dictionary _allocations; - private readonly Dictionary _perThreadLastAllocation; - - public ProcessAllocations( int pid ) - { - _pid = pid; - _allocations = new Dictionary(); - _perThreadLastAllocation = new Dictionary(); - } - - public int Pid => _pid; - - public AllocationInfo GetAllocations( string typeName ) - { - return ( _allocations.TryGetValue( typeName, out var info ) ) ? info : null; - } - - public IEnumerable GetAllAllocations() - { - return _allocations.Values; - } - - public AllocationInfo AddAllocation( int threadID, ulong size, ulong count, string typeName ) - { - if( !_allocations.TryGetValue( typeName, out var info ) ) - { - info = new AllocationInfo( typeName ); - _allocations[typeName] = info; - } - - info.AddAllocation( size, count ); - - // the last allocation is still here without the corresponding stack - if( _perThreadLastAllocation.TryGetValue( threadID, out var lastAlloc ) ) - { - Console.WriteLine( "no stack for the last allocation" ); - } - - // keep track of the allocation for the given thread - // --> will be used when the corresponding call stack event will be received - _perThreadLastAllocation[threadID] = info; - - return info; - } - - public void AddStack( int threadID, AddressStack stack ) - { - if( _perThreadLastAllocation.TryGetValue( threadID, out var lastAlloc ) ) - { - lastAlloc.AddStack( stack ); - _perThreadLastAllocation.Remove( threadID ); - return; - } - - //Console.WriteLine("no last allocation for the stack event"); - } - } - - - public class AllocationInfo - { - private readonly string _typeName; - private ulong _size; - private ulong _count; - private List _stacks; - - internal AllocationInfo( string typeName ) - { - _typeName = typeName; - _stacks = new List(); - } - - public string TypeName => _typeName; - public ulong Count => _count; - public ulong Size => _size; - public IReadOnlyList Stacks => _stacks; - - internal void AddAllocation( ulong size, ulong count ) - { - _count += count; - _size += size; - } - - internal void AddStack( AddressStack stack ) - { - var info = GetInfo( stack ); - if( info == null ) - { - info = new StackInfo( stack ); - _stacks.Add( info ); - } - - info.Count++; - } - - private StackInfo GetInfo( AddressStack stack ) - { - for( int i = 0; i < _stacks.Count; i++ ) - { - var info = _stacks[i]; - if( stack.Equals( info.Stack ) ) - return info; - } - - return null; - } - } - - public class StackInfo - { - private readonly AddressStack _stack; - public ulong Count; - - internal StackInfo( AddressStack stack ) - { - Count = 0; - _stack = stack; - } - - public AddressStack Stack => _stack; - } -} diff --git a/prof/ProcessTypeMapping.cs b/prof/ProcessTypeMapping.cs deleted file mode 100644 index 81574cb..0000000 --- a/prof/ProcessTypeMapping.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Collections.Generic; - -namespace Tracing -{ - // Contains the mapping between type ID received by SampleObjectAllocation(Low/High) events - // and their name received by TypeBulkType events - public class ProcessTypeMapping - { - private readonly Dictionary _typesIdToName; - - public ProcessTypeMapping( int processId ) - { - ProcessId = processId; - _typesIdToName = new Dictionary(); - } - - public int ProcessId { get; set; } - - public string this[ulong id] - { - get - { - if( !_typesIdToName.ContainsKey( id ) ) - return null; - - return _typesIdToName[id]; - } - set - { - _typesIdToName[id] = value; - } - } - - } -} diff --git a/prof/Program.cs b/prof/Program.cs deleted file mode 100644 index 1403092..0000000 --- a/prof/Program.cs +++ /dev/null @@ -1,162 +0,0 @@ -using Microsoft.Diagnostics.Tracing.Session; -using ProfilerHelpers; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace Tracing -{ - static class Program - { - - - static public int CreateTracingSession( bool noSampling, bool sortBySize, int topTypesLimit ) - { - ShowHeader(); - - try - { - - TraceEventSession session = new TraceEventSession( - "Tracing", - TraceEventSessionOptions.Create - ); - - log.info( $"Create PerProcessProfilingState" ); - - using( var processes = new PerProcessProfilingState() ) - { - log.info( $"Create Memory profiler for session" ); - var profiler = new Memory( session, processes ); - - log.info( $"Start task" ); - var task = profiler.StartAsync( noSampling ); - - /* - log.info( $"await the Continue" ); - await task.ContinueWith( ( t ) => { - log.info( $"Task is done, Dispose" ); - session.Dispose(); - } ); - */ - - //log.info("Press ENTER to stop memory profiling"); - //Console.ReadLine(); - - /* - try - { - await task; - ShowResults( processes, sortBySize, topTypesLimit ); - - return 0; - } - catch( Exception x ) - { - log.info( x.Message ); - ShowHelp(); - } - */ - } - - return -1; - } - catch( Exception x ) - { - log.info( x.Message ); - ShowHelp(); - } - - return -2; - } - - private static void ShowResults( PerProcessProfilingState processes, bool sortBySize, int topTypesLimit ) - { - foreach( var pid in processes.Allocations.Keys ) - { - // skip processes without symbol resolution - if( !processes.Methods.ContainsKey( pid ) ) - continue; - - // skip processes without allocations - if( !processes.Allocations[pid].GetAllAllocations().Any() ) - continue; - - ShowResults( GetProcessName( pid, processes.Names ), processes.Methods[pid], processes.Allocations[pid], sortBySize, topTypesLimit ); - } - } - - private static string GetProcessName( int pid, Dictionary names ) - { - if( names.TryGetValue( pid, out var name ) ) - return name; - - return pid.ToString(); - } - - private static void ShowResults( string name, MethodStore methods, ProcessAllocations allocations, bool sortBySize, int topTypesLimit ) - { - log.info( $"Memory allocations for {name}" ); - log.info( $"" ); - log.info( "---------------------------------------------------------" ); - log.info( " Count Size Type" ); - log.info( "---------------------------------------------------------" ); - IEnumerable types = ( sortBySize ) - ? allocations.GetAllAllocations().OrderByDescending( a => a.Size ) - : allocations.GetAllAllocations().OrderByDescending( a => a.Count ) - ; - if( topTypesLimit != -1 ) - types = types.Take( topTypesLimit ); - - foreach( var allocation in types ) - { - log.info( $"{allocation.Count,9} {allocation.Size,11} {allocation.TypeName}" ); - - log.info( $"" ); - DumpStacks( allocation, methods ); - log.info( $"" ); - } - log.info( $"" ); - log.info( $"" ); - } - - private static void DumpStacks( AllocationInfo allocation, MethodStore methods ) - { - var stacks = allocation.Stacks.OrderByDescending( s => s.Count ).Take( 10 ); - foreach( var stack in stacks ) - { - log.info( $"{stack.Count,6} allocations" ); - log.info( "----------------------------------" ); - DumpStack( stack.Stack, methods ); - log.info( $"" ); - } - } - - private static void DumpStack( AddressStack stack, MethodStore methods ) - { - var callstack = stack.Stack; - for( int i = 0; i < Math.Min( 10, callstack.Count ); i++ ) - { - log.info( $" {methods.GetFullName( callstack[i] )}" ); - } - } - - private static void ShowHeader() - { - log.info( "Tracing v1.0.0 - Sampled memory profiler for .NET applications" ); - log.info( "by Christophe Nasarre" ); - log.info( $"" ); - } - private static void ShowHelp() - { - log.info( $"" ); - log.info( "Tracing shows sampled allocations of a given .NET application." ); - log.info( "Usage: Tracing [-a (all allocations)] [-c (sort by count instead of default by size)] [-t ]" ); - log.info( " Ex: Tracing -t -1 (all types sampled allocations sorted by size)" ); - log.info( " Ex: Tracing -c -t 10 (allocations for top 10 types sorted by count)" ); - log.info( $"" ); - } - } -} diff --git a/reflect/refl.cs b/reflect/refl.cs index 348447e..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 ) { { @@ -196,7 +295,7 @@ static public class refl return first; } - LogGC.RegisterObjectId( t ); + //LogGC.RegisterObjectId( t ); lock( 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 b35c292..44c1043 100644 --- a/res/Resource.cs +++ b/res/Resource.cs @@ -6,13 +6,18 @@ using System.Reflection; using System.Collections.Immutable; using System.Runtime.CompilerServices; using System.Threading; -using Microsoft.CodeAnalysis; // Note: This was in the original, but seems unused. Keep for now. 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 ); @@ -34,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; @@ -54,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 ); @@ -67,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 ); + } } /// @@ -77,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; @@ -89,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}" ); @@ -111,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 ) { @@ -120,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; @@ -132,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); } */ @@ -160,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 ) { @@ -187,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'. @@ -210,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 { @@ -217,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 ); @@ -228,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 )!; @@ -336,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() { @@ -377,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 ); @@ -398,7 +444,6 @@ public static class Mgr log.info( $"Cached {typeof( T ).Name}: {filename} ({reason})" ); } - /// /// Tries to retrieve a resource from the cache. /// @@ -423,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; @@ -438,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/scr/Script.cs b/scr/Script.cs index 78df00c..5f217f0 100644 --- a/scr/Script.cs +++ b/scr/Script.cs @@ -3,7 +3,7 @@ #nullable enable - +#if SCRIPTS using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Emit; @@ -20,43 +20,64 @@ using System.Threading; public class MemorySourceText : SourceText { - public override char this[int position] => ' '; + private readonly string _content; + + public MemorySourceText(string content = "") + { + _content = content ?? string.Empty; + } + + public override char this[int position] => _content[position]; public override Encoding? Encoding => Encoding.UTF8; - public override int Length => 0; + public override int Length => _content.Length; - public override void CopyTo( int sourceIndex, char[] destination, int destinationIndex, int count ) + public override void CopyTo(int sourceIndex, char[] destination, int destinationIndex, int count) { + if (destination == null) + throw new ArgumentNullException(nameof(destination)); + if (sourceIndex < 0 || sourceIndex >= _content.Length) + throw new ArgumentOutOfRangeException(nameof(sourceIndex)); + + if (destinationIndex < 0 || destinationIndex >= destination.Length) + throw new ArgumentOutOfRangeException(nameof(destinationIndex)); + + if (count < 0) + throw new ArgumentOutOfRangeException(nameof(count)); + + if (sourceIndex + count > _content.Length || destinationIndex + count > destination.Length) + throw new ArgumentException("Source or destination range is out of bounds."); + + _content.CopyTo(sourceIndex, destination, destinationIndex, count); } } public class MemoryRefResolver : SourceReferenceResolver { - public override bool Equals( object? other ) + public override bool Equals(object? other) { - return false; + return other is MemoryRefResolver; } public override int GetHashCode() { - return 0; + return typeof(MemoryRefResolver).GetHashCode(); } - private MemoryStream _fakeMS = new MemoryStream(); + private readonly MemoryStream _fakeMS = new MemoryStream(); - public override string? NormalizePath( string? path, string? baseFilePath ) => path; + public override string? NormalizePath(string? path, string? baseFilePath) => path; - public override Stream OpenRead( string resolvedPath ) => _fakeMS; + public override Stream OpenRead(string resolvedPath) => _fakeMS; - private MemorySourceText _fakeMST = new(); + private readonly MemorySourceText _fakeMST = new(); - public override SourceText ReadText( string resolvedPath ) => _fakeMST; - - public override string? ResolveReference( string? path, string? baseFilePath ) => path; + public override SourceText ReadText(string resolvedPath) => _fakeMST; + public override string? ResolveReference(string? path, string? baseFilePath) => path; } @@ -204,7 +225,7 @@ public static class scr CompileFile( filename, ( ass ) => { s_fnAss( ass ); }, ( diags ) => { } ); } - public static void CompileFile( string filename, Action onSuccess, Action> onFailure, Platform platform = Platform.X86 ) + public static void CompileFile( string filename, Action onSuccess, Action> onFailure/* PORT , Platform platform = Platform.X86*/ ) { var fullpath = Path.GetFullPath( filename ); @@ -215,7 +236,7 @@ public static class scr Compile( sourceText, fullpath, onSuccess, onFailure ); } - public static void Compile( string str, string uniquePath, Action onSuccess, Action> onFailure, Platform platform = Platform.X86 ) + public static void Compile( string str, string uniquePath, Action onSuccess, Action> onFailure /*, Platform platform = Platform.X86*/ ) { var sourceText = SourceText.From( str ); @@ -223,7 +244,7 @@ public static class scr } - public static void Compile( SourceText sourceText, string uniquePath, Action onSuccess, Action> onFailure, Platform platform = Platform.X86 ) + public static void Compile( SourceText sourceText, string uniquePath, Action onSuccess, Action> onFailure /*, Platform platform = Platform.X86*/ ) { string assemblyName = Path.GetRandomFileName(); @@ -237,7 +258,7 @@ public static class scr using MemoryStream ms = new(); using MemoryStream pdb = new(); - var result = CompileAndEmit( assemblyName, new[] { syntaxTree }, ms, pdb, platform ); + var result = CompileAndEmit( assemblyName, new[] { syntaxTree }, ms, pdb ); if( !result.Success ) { @@ -269,7 +290,7 @@ public static class scr } } - private static EmitResult CompileAndEmit( string assemblyName, SyntaxTree[] syntaxTrees, MemoryStream ms, MemoryStream pdb, Platform platform ) + private static EmitResult CompileAndEmit( string assemblyName, SyntaxTree[] syntaxTrees, MemoryStream ms, MemoryStream pdb /*, Platform platform*/ ) { MemoryRefResolver memRef = new(); @@ -281,7 +302,7 @@ public static class scr options: new CSharpCompilationOptions( OutputKind.DynamicallyLinkedLibrary, sourceReferenceResolver: memRef, optimizationLevel: OptimizationLevel.Release, - platform: platform, + //platform: platform, specificDiagnosticOptions: new Dictionary { { "CS1701", ReportDiagnostic.Suppress } @@ -358,3 +379,4 @@ public static class scr } } +#endif 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 ) { diff --git a/sharplib.sln b/sharplib.sln deleted file mode 100644 index 4af1880..0000000 --- a/sharplib.sln +++ /dev/null @@ -1,25 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.5.002.0 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharpLib", "SharpLib.csproj", "{CC1801F8-7270-47A2-AF73-CCE2900549A9}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {CC1801F8-7270-47A2-AF73-CCE2900549A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CC1801F8-7270-47A2-AF73-CCE2900549A9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CC1801F8-7270-47A2-AF73-CCE2900549A9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CC1801F8-7270-47A2-AF73-CCE2900549A9}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {E2F905DE-69F3-48F3-943C-B8544945C2E9} - EndGlobalSection -EndGlobal diff --git a/sharplib_be.sln b/sharplib_be.sln deleted file mode 100644 index 1099e88..0000000 --- a/sharplib_be.sln +++ /dev/null @@ -1,25 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.5.002.0 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharpLib", "SharpLib.csproj", "{8D6C0DEE-23AB-41CA-99B0-4575ECBB41F6}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {8D6C0DEE-23AB-41CA-99B0-4575ECBB41F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8D6C0DEE-23AB-41CA-99B0-4575ECBB41F6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8D6C0DEE-23AB-41CA-99B0-4575ECBB41F6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8D6C0DEE-23AB-41CA-99B0-4575ECBB41F6}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {2C2EFBD0-C7A0-4D9C-B6CD-68E0112B6786} - EndGlobalSection -EndGlobal diff --git a/srl/srl.Core.cs b/srl/srl.Core.cs deleted file mode 100644 index 05016fa..0000000 --- a/srl/srl.Core.cs +++ /dev/null @@ -1,375 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Reflection; -using System.Text; - -namespace srl; - -// --- INTERFACES --- - -public interface Driver -{ - // Structural - void BeginScope( string name, Type type, int id ); - void EndScope(); - void BeginCollection( string name, int count ); - void EndCollection(); - - // Data - void WriteAttr( string name, string value ); // Primitives & Compact Strings - void WriteRef( string name, int id ); // DAG/Cycle Reference - - // Metadata Hook - void OnProp( MemberInfo member, string name ); -} - -public interface IInput -{ - string? Value { get; } - bool IsLeaf { get; } - IInput? GetAttr( string name ); - IInput? GetChild( string name ); -} - -// --- THE MODEL (Introspection) --- - -public class TypePlan -{ - public string Name; - public bool IsCollection; - public bool IsHybrid; // True if Type has a StringParser registered - public StringParser Parser; // The "Compact" converter - public List Props = new(); -} - -public class PropPlan -{ - public string Name; - public string MemberName; - public Type Type; - public bool IsSmart; // Primitive/Enum/String - public MemberInfo Info; - - // Fast Accessors - public Func Getter; - public Action Setter; -} - -public static class Model -{ - private static Dictionary _cache = new(); - - public static TypePlan Get( Type t ) - { - if( _cache.TryGetValue( t, out var plan ) ) - return plan; - - plan = new TypePlan { Name = t.Name }; - - // 1. Check for Custom Parsers (Hybrid Mode) - if( Parsers.TryGet( t, out var parser ) ) - { - plan.IsHybrid = true; - plan.Parser = parser; - } - - // 2. Check for Collections - if( t != typeof( string ) && typeof( IEnumerable ).IsAssignableFrom( t ) ) - { - plan.IsCollection = true; - _cache[t] = plan; - return plan; - } - - // 3. Scan Properties - foreach( var p in t.GetProperties( BindingFlags.Public | BindingFlags.Instance ) ) - { - if( Attribute.IsDefined( p, typeof( IgnoreAttribute ) ) ) - continue; - - var prop = new PropPlan - { - Name = p.Name, // Default to PascalCase, Drivers can lower if needed - MemberName = p.Name, - Type = p.PropertyType, - Info = p, - Getter = ( o ) => p.GetValue( o ), - Setter = ( o, v ) => p.SetValue( o, v ) - }; - - // Override name - var nameAttr = p.GetCustomAttribute(); - if( nameAttr != null ) - prop.Name = nameAttr.Name; - - // Is "Smart" (Atomic)? - prop.IsSmart = prop.Type.IsPrimitive || prop.Type.IsEnum || - prop.Type == typeof( string ) || prop.Type == typeof( Guid ); - - plan.Props.Add( prop ); - } - - _cache[t] = plan; - return plan; - } -} - -// --- ATTRIBUTES --- -public class IgnoreAttribute : Attribute { } -public class NameAttribute : Attribute { public string Name; public NameAttribute( string n ) => Name = n; } - -// --- UTILITIES --- - -public struct StringParser -{ - public Func To; - public Func From; -} - -public static class Parsers -{ - private static Dictionary _registry = new(); - - public static void Register( Func to, Func from ) - { - _registry[typeof( T )] = new StringParser { To = o => to( (T)o ), From = s => from( s ) }; - } - - public static bool TryGet( Type t, out StringParser p ) - { - if( _registry.TryGetValue( t, out p ) ) - return true; - // Auto-Discovery could go here (Static Parse methods) - return false; - } -} - -public static class Binder -{ - // Handles "Cool.Rare" dot notation - public static void Apply( object root, string path, string value ) - { - var current = root; - var parts = path.Split( '.' ); - - for( int i = 0; i < parts.Length; i++ ) - { - var part = parts[i]; - var isLast = i == parts.Length - 1; - var plan = Model.Get( current.GetType() ); - - // Case-Insensitive Match - var prop = plan.Props.Find( p => p.Name.Equals( part, StringComparison.OrdinalIgnoreCase ) ); - if( prop == null ) - return; - - if( isLast ) - { - var val = ParseUtils.Convert( value, prop.Type ); - prop.Setter( current, val ); - } - else - { - var next = prop.Getter( current ); - if( next == null ) - { - next = Activator.CreateInstance( prop.Type ); - prop.Setter( current, next ); - } - current = next; - } - } - } -} - -public static class ParseUtils -{ - public static object Convert( string raw, Type t ) - { - if( t == typeof( string ) ) - return raw; - if( t == typeof( int ) ) - return int.Parse( raw ); - if( t == typeof( float ) ) - return float.Parse( raw.Replace( "f", "" ) ); - if( t == typeof( bool ) ) - return bool.Parse( raw ); - if( t.IsEnum ) - return Enum.Parse( t, raw ); - // Fallback to TypeConverter - var cv = TypeDescriptor.GetConverter( t ); - if( cv != null && cv.CanConvertFrom( typeof( string ) ) ) - return cv.ConvertFrom( raw ); - return null; - } - - public static void CopyFields( object src, object dst ) - { - foreach( var p in src.GetType().GetProperties() ) - if( p.CanRead && p.CanWrite ) - p.SetValue( dst, p.GetValue( src ) ); - } -} - -// --- THE ENGINE (Walker & Loader) --- - -public static class Walker -{ - public class Context - { - private Dictionary _seen = new( ReferenceEqualityComparer.Instance ); - private int _nextId = 1; - public (int, bool) GetId( object o ) - { - if( _seen.TryGetValue( o, out var id ) ) - return (id, false); - _seen[o] = _nextId; - return (_nextId++, true); - } - } - - public static void Serialize( object root, Driver d ) - { - if( root == null ) - return; - SerializeRecursive( root, d, new Context(), "root", null ); - } - - private static void SerializeRecursive( object obj, Driver d, Context ctx, string name, MemberInfo? member ) - { - if( obj == null ) - return; - - Type type = obj.GetType(); - var plan = Model.Get( type ); - - // STRATEGY 1: COMPACT (Hybrid Parser) - if( plan.IsHybrid ) - { - if( member != null ) - d.OnProp( member, name ); - d.WriteAttr( name, plan.Parser.To( obj ) ); - return; - } - - // STRATEGY 2: DAG CHECK - bool isRef = !type.IsValueType && type != typeof( string ); - if( isRef ) - { - var (id, isNew) = ctx.GetId( obj ); - if( !isNew ) - { d.WriteRef( name, id ); return; } - if( member != null ) - d.OnProp( member, name ); - d.BeginScope( name, type, id ); - } - else - { - if( member != null ) - d.OnProp( member, name ); - d.BeginScope( name, type, 0 ); - } - - // STRATEGY 3: COLLECTIONS - if( plan.IsCollection ) - { - var list = (IEnumerable)obj; - int count = 0; // Simple count (could optimize for ICollection) - foreach( var _ in list ) - count++; - - d.BeginCollection( name, count ); - foreach( var item in list ) - SerializeRecursive( item, d, ctx, "item", null ); - d.EndCollection(); - } - else - { - // STRATEGY 4: STANDARD OBJECT - foreach( var prop in plan.Props ) - { - var val = prop.Getter( obj ); - if( prop.IsSmart ) - { - d.OnProp( prop.Info, prop.Name ); - d.WriteAttr( prop.Name, val?.ToString() ?? "" ); - } - else - { - if( val != null ) - SerializeRecursive( val, d, ctx, prop.Name, prop.Info ); - } - } - } - d.EndScope(); - } -} - -public static class Loader -{ - public static void Load( object target, IInput input ) - { - if( target == null || input == null ) - return; - var plan = Model.Get( target.GetType() ); - - // 1. HYBRID PARSE (Compact String) - // If we have a parser AND input is just a value "1,1" - if( plan.IsHybrid && input.IsLeaf && !string.IsNullOrWhiteSpace( input.Value ) ) - { - try - { - var newObj = plan.Parser.From( input.Value ); - ParseUtils.CopyFields( newObj, target ); - return; - } - catch { } - } - - // 2. STRUCTURAL MAP - foreach( var prop in plan.Props ) - { - // Look for Attribute OR Child Element - var sub = input.GetAttr( prop.Name ) ?? input.GetChild( prop.Name ); - - // Look for Dot Notation (e.g. "Pos.X") in attributes - // (Note: This simple loop doesn't scan ALL attrs for dots, - // it relies on the caller or specific recursive logic. - // For full dot support on root, we need to iterate input attributes if possible. - // But 'Binder' below handles it if we pass the specific attr key). - - if( sub != null ) - { - if( prop.IsSmart ) - { - if( sub.Value != null ) - prop.Setter( target, ParseUtils.Convert( sub.Value, prop.Type ) ); - } - else - { - var child = prop.Getter( target ); - if( child == null ) - { - child = Activator.CreateInstance( prop.Type ); - prop.Setter( target, child ); - } - Load( child, sub ); - } - } - } - } - - // Helper to scan all attributes on an element for "Cool.Rare" patterns - public static void LoadDotNotations( object target, IEnumerable<(string k, string v)> attrs ) - { - foreach( var (k, v) in attrs ) - { - if( k.Contains( '.' ) ) - Binder.Apply( target, k, v ); - } - } -} - diff --git a/srl/srl.Debug.cs b/srl/srl.Debug.cs deleted file mode 100644 index 0012b07..0000000 --- a/srl/srl.Debug.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; -using System.Reflection; -using System.Text; - -namespace srl.Debug; - -public class DebugDriver : Driver -{ - private StringBuilder _sb = new(); - private int _indent = 0; - - public override string ToString() => _sb.ToString(); - - private void Line( string s ) => _sb.AppendLine( new string( ' ', _indent * 2 ) + s ); - - public void BeginScope( string name, Type type, int id ) - { - var refStr = id > 0 ? $" #{id}" : ""; - Line( $"[{name}] <{type.Name}>{refStr}" ); - _indent++; - } - - public void EndScope() => _indent--; - - public void BeginCollection( string name, int count ) - { - Line( $"[{name}] (Count: {count})" ); - _indent++; - } - - public void EndCollection() => _indent--; - - public void WriteAttr( string name, string value ) - { - Line( $"{name} = {value}" ); - } - - public void WriteRef( string name, int id ) - { - Line( $"{name} -> See #{id}" ); - } - - public void OnProp( MemberInfo m, string name ) - { - // Could log attributes here, e.g. [Tooltip] - } -} diff --git a/srl/srl.Xml.cs b/srl/srl.Xml.cs deleted file mode 100644 index 4101906..0000000 --- a/srl/srl.Xml.cs +++ /dev/null @@ -1,131 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Xml.Linq; -using System.Reflection; - -namespace srl.Xml; - -// --- INPUT ADAPTER --- -public class XmlAdapter : IInput -{ - private XElement _e; - private XAttribute _a; - - public XmlAdapter( XElement e ) => _e = e; - public XmlAdapter( XAttribute a ) => _a = a; - - public string? Value => _e?.Value ?? _a?.Value; - public bool IsLeaf => _a != null || ( _e != null && !_e.HasElements ); - - public IInput? GetAttr( string name ) - { - if( _e == null ) - return null; - // Case-Insensitive search - var a = _e.Attribute( name ) ?? _e.Attribute( name.ToLower() ); - return a != null ? new XmlAdapter( a ) : null; - } - - public IInput? GetChild( string name ) - { - if( _e == null ) - return null; - - var c = _e.Element( name ) ?? _e.Element( name.ToLower() ); - - return c != null ? new XmlAdapter( c ) : null; - } - - // Extra helper for Dot Notation scanning - public IEnumerable<(string, string)> GetAllAttrs() - { - if( _e == null ) - yield break; - foreach( var a in _e.Attributes() ) - yield return (a.Name.LocalName, a.Value); - } -} - -// --- OUTPUT DRIVER --- -public class XmlDriver : Driver -{ - private Stack _stack = new(); - private XDocument _doc; - - public XDocument Document => _doc; - - public XmlDriver() - { - _doc = new XDocument(); - } - - public void BeginScope( string name, Type type, int id ) - { - var el = new XElement( name ); - - // Polymorphism Metadata (if needed, e.g. ) - // el.Add(new XAttribute("_type", type.Name)); - - if( id > 0 ) - el.Add( new XAttribute( "_id", id ) ); // DAG ID - - if( _stack.Count > 0 ) - _stack.Peek().Add( el ); - else - _doc.Add( el ); - - _stack.Push( el ); - } - - public void EndScope() => _stack.Pop(); - - public void BeginCollection( string name, int count ) - { - // XML doesn't strictly need array wrappers, but it helps structure - // We use the same BeginScope logic effectively - var el = new XElement( name, new XAttribute( "_count", count ) ); - if( _stack.Count > 0 ) - _stack.Peek().Add( el ); - _stack.Push( el ); - } - - public void EndCollection() => _stack.Pop(); - - public void WriteAttr( string name, string value ) - { - if( _stack.Count == 0 ) - return; - _stack.Peek().Add( new XAttribute( name, value ) ); - } - - public void WriteRef( string name, int id ) - { - var el = new XElement( name, new XAttribute( "_ref", id ) ); - _stack.Peek().Add( el ); - } - - public void OnProp( MemberInfo m, string name ) { /* Optional: Write tooltips/comments */ } -} - -// --- FACADE --- -public static class XmlSerializer -{ - public static string Serialize( object obj ) - { - var driver = new XmlDriver(); - srl.Walker.Serialize( obj, driver ); - return driver.Document.ToString(); - } - - public static void Deserialize( object root, string xml ) - { - var doc = XDocument.Parse( xml ); - var adapter = new XmlAdapter( doc.Root ); - - // 1. Standard Load - srl.Loader.Load( root, adapter ); - - // 2. Dot Notation Pass (MyBag Cool.Rare="Changed") - srl.Loader.LoadDotNotations( root, adapter.GetAllAttrs() ); - } -} diff --git a/task/Task.cs b/task/Task.cs deleted file mode 100644 index f5bfdb7..0000000 --- a/task/Task.cs +++ /dev/null @@ -1,16 +0,0 @@ - -using System.Threading.Tasks; -using System.Threading; -using System.Diagnostics; - - -namespace lib; - - -static public class Task -{ - - - - -} diff --git a/tests/Tests.cs b/tests/Tests.cs deleted file mode 100644 index ca21138..0000000 --- a/tests/Tests.cs +++ /dev/null @@ -1,125 +0,0 @@ - - - - - - -using System.Diagnostics; -using System.IO; - -namespace test; - - -public record class SimpleImmutable( string Name, int Age ) : io.Timed; - -static public class XmlFormatter2 -{ - - public class ClassWithProperties - { - public string ActualProperty { get; set; } = "test_ActualProperty_set_inline"; - public string ActualProperty_NotSerialized { get; set; } = "ActualProperty_NotSerialized"; - } - - [ser.Ser( Types = ser.Types.Implied )] - public partial class ClassContainsClassWithProp - { - - [ser.Do] - public bool doBool = true; - - [ser.ChildPropsAttribute( "ActualProperty" )] - public ClassWithProperties propHolder = new(); - - public string doNotSerialize = "test_do_not_serialize"; - - } - - [ser.Ser] - public class ClassHasFields - { - public ClassContainsClassWithProp prop = new(); - }; - - public static void Serialization() - { - - lib.XmlFormatter2Cfg cfg = new() - { - - }; - - - - - ClassHasFields classHasFields = new() - { - prop = new() - { - propHolder = new() - { - ActualProperty = "ActualProperty_set_in_cons" - } - } - }; - - Debug.Assert( classHasFields.prop.propHolder.ActualProperty == "ActualProperty_set_in_cons" ); - - - var memStream = new MemoryStream(); - - { - var xml = new lib.XmlFormatter2( cfg ); - xml.Serialize( memStream, classHasFields ); - } - - memStream.Position = 0; - - var strXml = System.Text.Encoding.UTF8.GetString( memStream.ToArray() ); - - var badXml = "\n \n"; - /* - - - - -*/ - Debug.Assert( strXml != badXml ); - - memStream.Position = 0; - - var classHasFields2 = new ClassHasFields(); - classHasFields2.prop.propHolder.ActualProperty_NotSerialized = "ActualProperty_NotSerialized_set_in_test_01"; - - Debug.Assert( classHasFields2.prop.propHolder.ActualProperty == "test_ActualProperty_set_inline" ); - Debug.Assert( classHasFields2.prop.propHolder.ActualProperty_NotSerialized == "ActualProperty_NotSerialized" ); - - - { - var xml = new lib.XmlFormatter2( cfg ); - classHasFields2 = xml.Deserialize( memStream ); - } - - - Debug.Assert( classHasFields2.prop.propHolder.ActualProperty == "ActualProperty_set_in_cons" ); - Debug.Assert( classHasFields2.prop.propHolder.ActualProperty_NotSerialized == "ActualProperty_NotSerialized_set_in_test_01" ); - - memStream.Position = 0; - - var classHasFields3 = new ClassHasFields(); - - { - var xml = new lib.XmlFormatter2( cfg ); - xml.DeserializeInto( memStream, classHasFields3 ); - } - - Debug.Assert( classHasFields3.prop.propHolder.ActualProperty == "ActualProperty_set_in_cons" ); - Debug.Assert( classHasFields3.prop.propHolder.ActualProperty_NotSerialized == "ActualProperty_NotSerialized_set_in_test_01" ); - - - } - - - -} - diff --git a/time/Time.cs b/time/Time.cs index 3ce825e..ee14811 100644 --- a/time/Time.cs +++ b/time/Time.cs @@ -3,6 +3,8 @@ +using System; + namespace time; diff --git a/util/Exec.cs b/util/Exec.cs index 136e398..d0f77ca 100644 --- a/util/Exec.cs +++ b/util/Exec.cs @@ -1,6 +1,7 @@ +using System; using System.Diagnostics; namespace lib;