sharplib/imm/Imm.cs

374 lines
9.3 KiB
C#

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using lib;
namespace imm;
/*
T O D O :
T O D O :
T O D O :
x) Add unit tests for all this. This will definitely benefit from them
*/
static public class Util
{
//This can handle both Timed and Recorded
static public T Process<T>( ref T obj, Func<T, T> fn, string reason = "",
[CallerMemberName] string dbgName = "",
[CallerFilePath] string dbgPath = "",
[CallerLineNumber] int dbgLine = 0,
[CallerArgumentExpression("fn")]
string dbgExp = default )
where T : Recorded<T>
{
obj = obj.Process( fn, reason, dbgName, dbgPath, dbgLine, dbgExp );
return obj;
}
static public T LightProcess<T>( ref T obj, Func<T, T> fn, string reason = "",
[CallerMemberName] string dbgName = "",
[CallerFilePath] string dbgPath = "",
[CallerLineNumber] int dbgLine = 0,
[CallerArgumentExpression("fn")]
string dbgExp = default )
where T : Versioned<T>
{
obj = obj.Process( fn, reason );
return obj;
}
}
public interface Meta
{
public uint Version => 0;
public string Reason => "";
public string Expression => "";
public string MemberName => "";
public string FilePath => "";
public int LineNumber => -1;
public DateTime CreatedAt => DateTime.MinValue;
public DateTime TouchedAt => DateTime.MinValue;
}
public interface Imm
{
public Meta Meta { get; }
public object Record( string reason = "",
[CallerMemberName] string dbgName = "",
[CallerFilePath] string dbgPath = "",
[CallerLineNumber] int dbgLine = 0
);
}
//[lib.Ser( Types = lib.Types.None )]
public record class Versioned<T> : Imm
where T : Versioned<T>
{
public delegate void ChangeDelegate( T ZZOld, T next );
public record class MetaData : Meta
{
public uint Version { get; internal set; } = 0;
public string Reason { get; internal set; } = "";
public MetaData() { }
}
protected Versioned( )
{
MetaStorage = new MetaData { Version = 1, Reason = $"Creation" };
}
internal Versioned( MetaData meta )
{
MetaStorage = meta;
}
virtual public T Record(
string reason = "",
[CallerMemberName] string dbgName = "",
[CallerFilePath] string dbgPath = "",
[CallerLineNumber] int dbgLine = 0
)
{
return Process( t => t, reason );
}
protected MetaData MetaStorage = new();
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
public MetaData Meta => MetaStorage;
Meta Imm.Meta => MetaStorage;
[lib.Dont]
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
internal ChangeDelegate OnChange = (old, cur) => {};
public void AddOnChange( ChangeDelegate fn,
string reason,
[CallerMemberName] string dbgName = "",
[CallerFilePath] string dbgPath = "",
[CallerLineNumber] int dbgLine = 0
)
{
log.debug( $"ADD {log.whatFile(dbgPath)}({dbgLine}): {dbgName} added OnChange bcs {reason}" );
OnChange += fn;
}
public void RemOnChange( ChangeDelegate fn,
[CallerMemberName] string dbgName = "",
[CallerFilePath] string dbgPath = "",
[CallerLineNumber] int dbgLine = 0
)
{
log.debug( $"REM {log.whatFile(dbgPath)}({dbgLine}): {dbgName} removing OnChange" );
OnChange -= fn;
}
public T Process( Func<T, T> fn, string reason = "" )
{
var newT = fn( ( T )this );
return newT with
{
MetaStorage = Meta with
{
Version = newT.Meta.Version + 1,
Reason = reason,
}
};
}
object Imm.Record( string reason, string dbgName, string dbgPath, int dbgLine ) => Record( reason, dbgName, dbgPath, dbgLine );
//public object Record( string reason = "", [CallerMemberName] string dbgName = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = 0 ) => Recorded( );
}
//[lib.Ser( Types = lib.Types.None )]
public record class Recorded<T> : Versioned<T>, imm.Imm
where T : Recorded<T>
{
new public record class MetaData : Versioned<T>.MetaData
{
[lib.Dont]
public T? ZZOld { get; internal set; }
public T? Old => ZZOld;
public string Expression { get; internal set; } = "";
public string MemberName { get; internal set; } = "";
public string FilePath { get; internal set; } = "";
public int LineNumber { get; internal set; } = -1;
public MetaData() { }
}
public Recorded() : this( new MetaData() )
{
}
public Recorded(MetaData meta) : base( meta )
{
}
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
new public MetaData Meta => MetaStorage as MetaData;
override public T Record(
string reason = "",
[CallerMemberName] string dbgName = "",
[CallerFilePath] string dbgPath = "",
[CallerLineNumber] int dbgLine = 0
)
{
return Process( t => t, reason, dbgName, dbgPath, dbgLine );
}
virtual public T Process( T next,
string reason = "",
[CallerMemberName] string dbgName = "",
[CallerFilePath] string dbgPath = "",
[CallerLineNumber] int dbgLine = 0,
[CallerArgumentExpression("next")]
string dbgExp = default
)
{
return Process( ( old ) => next, reason, dbgName, dbgPath, dbgLine, dbgExp );
}
virtual public T Process( Func<T, T> fn,
string reason = "",
[CallerMemberName] string dbgName = "",
[CallerFilePath] string dbgPath = "",
[CallerLineNumber] int dbgLine = 0,
[CallerArgumentExpression("fn")]
string dbgExp = default
)
{
var orig = ( T )this;
var next = fn( orig );
if( object.ReferenceEquals( orig, next) )
return next;
var ret = next with
{
//Do the Versioned code here
MetaStorage = Meta with
{
Version = orig.Meta.Version + 1,
Reason = !string.IsNullOrWhiteSpace( reason ) ? reason : next.Meta.Reason,
ZZOld = orig,
MemberName = dbgName,
FilePath = dbgPath,
LineNumber = dbgLine,
}
};
OnChange( orig, ret );
return ret;
}
}
public record class Timed<T> : Recorded<T>, imm.Imm
where T : Timed<T>
{
new public record class MetaData : Recorded<T>.MetaData
{
public readonly DateTime CreatedAt = DateTime.Now;
public DateTime TouchedAt { get; internal set; } = DateTime.Now;
}
public Timed() : this( new MetaData() )
{
}
public Timed( MetaData meta ) : base( meta )
{
}
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
new public MetaData Meta => MetaStorage as MetaData;
public TimeSpan Since => Meta.TouchedAt - Meta.Old?.Meta.TouchedAt ?? TimeSpan.MaxValue;
public void CallOnChange()
{
OnChange( Meta.Old, (T)this );
}
override public T Record(
string reason = "",
[CallerMemberName] string dbgName = "",
[CallerFilePath] string dbgPath = "",
[CallerLineNumber] int dbgLine = 0
)
{
return Process( t => t with { MetaStorage = t.Meta with { Reason = $"Record {reason}" }}, reason, dbgName, dbgPath, dbgLine );
}
override public T Process( T next,
string reason = "",
[CallerMemberName] string dbgName = "",
[CallerFilePath] string dbgPath = "",
[CallerLineNumber] int dbgLine = 0,
[CallerArgumentExpression("next")]
string dbgExp = default
)
{
return Process( ( old ) => next, reason, dbgName, dbgPath, dbgLine, dbgExp );
}
public U ProcessFn<U>( Func<U, U> fn,
string reason = "",
[CallerMemberName] string dbgName = "",
[CallerFilePath] string dbgPath = "",
[CallerLineNumber] int dbgLine = 0,
[CallerArgumentExpression("next")]
string dbgExp = default
)
where U : T
{
return (U)ProcessFn( fn, reason, dbgName, dbgPath, dbgLine, dbgExp );
}
override public T Process( Func<T, T> fn,
string reason = "",
[CallerMemberName] string dbgName = "",
[CallerFilePath] string dbgPath = "",
[CallerLineNumber] int dbgLine = 0,
[CallerArgumentExpression("fn")]
string dbgExp = default
)
=> ProcessFn( fn, reason, dbgName, dbgPath, dbgLine, dbgExp );
public T ProcessFn( Func<T, T> fn,
string reason = "",
[CallerMemberName] string dbgName = "",
[CallerFilePath] string dbgPath = "",
[CallerLineNumber] int dbgLine = 0,
[CallerArgumentExpression("fn")]
string dbgExp = default
)
{
var orig = ( T )this;
var next = fn( orig );
if( object.ReferenceEquals( orig, next) )
return next;
var ret = next with
{
MetaStorage = Meta with
{
//Versioned
Version = orig.Meta.Version + 1,
Reason = !string.IsNullOrWhiteSpace( reason ) ? reason : next.Meta.Reason,
//Recorded
MemberName = dbgName,
FilePath = dbgPath,
LineNumber = dbgLine,
ZZOld = orig,
//Timed
TouchedAt = DateTime.Now,
}
};
if( OnChange != null)
OnChange( orig, ret );
return ret;
}
}